如何使用添加到标准输入的命令行参数执行自我?
How to exec self with command-line arguments added to stdin?
编辑: 只是为了澄清,我想知道如何将内容管道实现到 exec
的流程,除了Perl 是否提供更好的方法来实现不涉及此技术的相同最终结果的问题。
用玩具 zsh
脚本示例更容易描述我想做什么:
#!/usr/bin/env zsh
# -----------------------------------------------------------------------------
# handle command-line arguments if any
# nb: ${(%):-%x} is zsh-speak for "yours truly"
# (( $# % 2 )) && (print -rl -- "$@"; [[ -t 0 ]] || cat) | exec ${(%):-%x}
(( $# > 0 )) && ([[ -t 0 ]] || cat; print -rl -- "$@") | exec ${(%):-%x}
# -----------------------------------------------------------------------------
# standard operation below this line
nl
上面的脚本几乎是 nl
("number lines") 实用程序的传递包装器,除了如果存在命令行参数,它将把它们附加到它的标准输入。例如:
$ seq 3 | /tmp/nlwrapper.sh
1 1
2 2
3 3
$ seq 3 | /tmp/nlwrapper.sh foo bar baz
1 1
2 2
3 3
4 foo
5 bar
6 baz
注意
命令行参数可以很容易地添加到标准输入(事实上,如果取消注释脚本的第 7 行,生成的脚本将分别根据它们的数量是奇数还是偶数,将命令行参数添加或附加到标准输入); 我对这两个功能都感兴趣。
脚本由两个完全独立的部分组成:一个处理命令行参数(如果有的话)的序言,和主体(在这个玩具中由单行组成的示例)负责脚本的主要功能(编号行); 这是一个基本的设计特点。
进一步详细说明这两点:"body" 部分对使用命令行参数的业务一无所知。实现这种命令行参数处理的代码几乎可以 "as-is" 添加到任何处理 stdin 的 zsh
脚本。此外,对命令行参数处理方式的更改(前置与附加等)使注释 # standard operation below this line
下方的所有内容保持不变。这两个部分真正相互独立。
上面的序言在 Perl 脚本中的等效项是什么?
我知道上面脚本的 Perl 等价物具有一般形式
#!/usr/bin/env perl
use strict;
use English;
# -----------------------------------------------------------------------------
# handle command-line arguments if any
if ( @ARGV > 0 ) {
# (mumble, mumble) ... -t STDIN ... exec $PROGRAM_NAME;
}
# -----------------------------------------------------------------------------
# standard operation below this line
printf "%6d\t$_", $., $_ while <>;
我的问题是实现这个位:
([[ -t 0 ]] || cat; print -rl -- "$@") |
我知道
- Perl 中的
[[ -t 0 ]]
测试是 -t STDIN
;
cat
部分可以用print while <>
实现;和
print -rl -- "$@"
位可以用 CORE::say for @ARGV
实现
我不知道如何将这些元素组合在一起以获得所需的功能。
除非 Perl 脚本需要像 shell 脚本一样神秘,否则我会这样做:
#!/usr/bin/env perl
use strict;
use warnings;
use autouse Carp => qw( croak );
use IO::Interactive qw( is_interactive );
run( \*STDIN, is_interactive() ? [] : (\@ARGV, [qw(ya ba da ba doo)]) );
sub run {
my $appender = mk_appender(@_);
printf "%6d\t%s", @$_ while $_ = $appender->();
}
sub mk_appender {
my $fh = shift;
my $line_number = 0;
my $i = 0;
my @readers = (
sub { scalar <$fh> },
(
map { my $argv = $_; sub {
($i < @$argv) ? $argv->[$i++] . "\n" : ();
}} @_
),
);
return sub {
@readers or return;
while ( @readers ) {
my $line = $readers[0]->();
return [++$line_number, $line] if defined $line;
shift @readers;
$i = 0;
}
return;
};
}
输出:
$ seq 3 | perl t.pl foo bar baz
1 1
2 2
3 3
4 foo
5 bar
6 baz
7 ya
8 ba
9 da
10 ba
11 doo
使用 Perl 提供的工具而不是尝试复制 shell 脚本的一些优点包括您可以避免产生额外的进程、读取整个标准输入两次等。
我还在How to sum data from multiple files in Perl?
中写了同时读取多个文件
如果您打算两次读取相同的输入,请参阅 pipe and Bidirectional communication with yourself。
使用具有一些常见 shell 功能的 cat
可能会更容易。 (此处使用bash
。)
cat <( seq 3 ) <( printf 'foo\nbar\nbaz\n' ) | prog
解决方案:
use POSIX qw( );
sub munge_stdin {
pipe(my $r, my $w) or die("pipe: $!");
$w->autoflush();
local $SIG{CHLD} = 'IGNORE';
defined( my $pid = fork() ) or die("fork: $!");
if (!$pid) {
eval {
close($r) or die("close pipe: $!");
while (<STDIN>) {
print($w $_) or die("print: $!");
}
for (@ARGV) {
print($w "$_\n") or die("print: $!");
}
POSIX::_exit(0);
};
warn($@);
POSIX::_exit(1);
}
close($w) or die("close pipe: $!");
open(STDIN, '<&', $r) or die("dup: $!");
@ARGV = ();
}
munge_stdin();
print while <>; # or: exec("cat") or die("exec: $!");
- 此解决方案在 Perl 脚本中支持
exec()
。
- 无论输入有多大,这个解决方案都不会死锁。
编辑: 只是为了澄清,我想知道如何将内容管道实现到 exec
的流程,除了Perl 是否提供更好的方法来实现不涉及此技术的相同最终结果的问题。
用玩具 zsh
脚本示例更容易描述我想做什么:
#!/usr/bin/env zsh
# -----------------------------------------------------------------------------
# handle command-line arguments if any
# nb: ${(%):-%x} is zsh-speak for "yours truly"
# (( $# % 2 )) && (print -rl -- "$@"; [[ -t 0 ]] || cat) | exec ${(%):-%x}
(( $# > 0 )) && ([[ -t 0 ]] || cat; print -rl -- "$@") | exec ${(%):-%x}
# -----------------------------------------------------------------------------
# standard operation below this line
nl
上面的脚本几乎是 nl
("number lines") 实用程序的传递包装器,除了如果存在命令行参数,它将把它们附加到它的标准输入。例如:
$ seq 3 | /tmp/nlwrapper.sh
1 1
2 2
3 3
$ seq 3 | /tmp/nlwrapper.sh foo bar baz
1 1
2 2
3 3
4 foo
5 bar
6 baz
注意
命令行参数可以很容易地添加到标准输入(事实上,如果取消注释脚本的第 7 行,生成的脚本将分别根据它们的数量是奇数还是偶数,将命令行参数添加或附加到标准输入); 我对这两个功能都感兴趣。
脚本由两个完全独立的部分组成:一个处理命令行参数(如果有的话)的序言,和主体(在这个玩具中由单行组成的示例)负责脚本的主要功能(编号行); 这是一个基本的设计特点。
进一步详细说明这两点:"body" 部分对使用命令行参数的业务一无所知。实现这种命令行参数处理的代码几乎可以 "as-is" 添加到任何处理 stdin 的 zsh
脚本。此外,对命令行参数处理方式的更改(前置与附加等)使注释 # standard operation below this line
下方的所有内容保持不变。这两个部分真正相互独立。
上面的序言在 Perl 脚本中的等效项是什么?
我知道上面脚本的 Perl 等价物具有一般形式
#!/usr/bin/env perl
use strict;
use English;
# -----------------------------------------------------------------------------
# handle command-line arguments if any
if ( @ARGV > 0 ) {
# (mumble, mumble) ... -t STDIN ... exec $PROGRAM_NAME;
}
# -----------------------------------------------------------------------------
# standard operation below this line
printf "%6d\t$_", $., $_ while <>;
我的问题是实现这个位:
([[ -t 0 ]] || cat; print -rl -- "$@") |
我知道
- Perl 中的
[[ -t 0 ]]
测试是-t STDIN
; cat
部分可以用print while <>
实现;和print -rl -- "$@"
位可以用CORE::say for @ARGV
实现
我不知道如何将这些元素组合在一起以获得所需的功能。
除非 Perl 脚本需要像 shell 脚本一样神秘,否则我会这样做:
#!/usr/bin/env perl
use strict;
use warnings;
use autouse Carp => qw( croak );
use IO::Interactive qw( is_interactive );
run( \*STDIN, is_interactive() ? [] : (\@ARGV, [qw(ya ba da ba doo)]) );
sub run {
my $appender = mk_appender(@_);
printf "%6d\t%s", @$_ while $_ = $appender->();
}
sub mk_appender {
my $fh = shift;
my $line_number = 0;
my $i = 0;
my @readers = (
sub { scalar <$fh> },
(
map { my $argv = $_; sub {
($i < @$argv) ? $argv->[$i++] . "\n" : ();
}} @_
),
);
return sub {
@readers or return;
while ( @readers ) {
my $line = $readers[0]->();
return [++$line_number, $line] if defined $line;
shift @readers;
$i = 0;
}
return;
};
}
输出:
$ seq 3 | perl t.pl foo bar baz 1 1 2 2 3 3 4 foo 5 bar 6 baz 7 ya 8 ba 9 da 10 ba 11 doo
使用 Perl 提供的工具而不是尝试复制 shell 脚本的一些优点包括您可以避免产生额外的进程、读取整个标准输入两次等。
我还在How to sum data from multiple files in Perl?
中写了同时读取多个文件如果您打算两次读取相同的输入,请参阅 pipe and Bidirectional communication with yourself。
使用具有一些常见 shell 功能的 cat
可能会更容易。 (此处使用bash
。)
cat <( seq 3 ) <( printf 'foo\nbar\nbaz\n' ) | prog
解决方案:
use POSIX qw( );
sub munge_stdin {
pipe(my $r, my $w) or die("pipe: $!");
$w->autoflush();
local $SIG{CHLD} = 'IGNORE';
defined( my $pid = fork() ) or die("fork: $!");
if (!$pid) {
eval {
close($r) or die("close pipe: $!");
while (<STDIN>) {
print($w $_) or die("print: $!");
}
for (@ARGV) {
print($w "$_\n") or die("print: $!");
}
POSIX::_exit(0);
};
warn($@);
POSIX::_exit(1);
}
close($w) or die("close pipe: $!");
open(STDIN, '<&', $r) or die("dup: $!");
@ARGV = ();
}
munge_stdin();
print while <>; # or: exec("cat") or die("exec: $!");
- 此解决方案在 Perl 脚本中支持
exec()
。 - 无论输入有多大,这个解决方案都不会死锁。