如何使用添加到标准输入的命令行参数执行自我?

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

注意

  1. 命令行参数可以很容易地添加到标准输入(事实上,如果取消注释脚本的第 7 行,生成的脚本将分别根据它们的数量是奇数还是偶数,将命令行参数添加或附加到标准输入); 我对这两个功能都感兴趣

  2. 脚本由两个完全独立的部分组成:一个处理命令行参数(如果有的话)的序言,和主体(在这个玩具中由单行组成的示例)负责脚本的主要功能(编号行); 这是一个基本的设计特点

进一步详细说明这两点:"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 -- "$@") |

我知道

  1. Perl 中的 [[ -t 0 ]] 测试是 -t STDIN;
  2. cat部分可以用print while <>实现;和
  3. print -rl -- "$@" 位可以用 CORE::say for @ARGV
  4. 实现

我不知道如何将这些元素组合在一起以获得所需的功能。

除非 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()
  • 无论输入有多大,这个解决方案都不会死锁。