如何使用添加到stdin的命令行参数来执行self?

时间:2017-05-03 13:56:22

标签: perl scripting exec command-line-arguments 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("数字行")实用程序的传递包装器,不同的是,如果存在命令行参数,它会将它们附加到到它的标准。例如:

$ 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. 命令行参数可以很容易地 prepended 到stdin(事实上,如果取消注释脚本的第7行,生成的脚本将会预先添加或追加stdin的命令行参数取决于它们的数字是奇数还是偶数); 我对两种功能感兴趣

  2. 该脚本由两个完全独立的部分组成:一个处理命令行参数的前导码(如果有的话)和正文(在这个玩具示例中由一行组成)它负责脚本的主要功能(编号行); 这是必不可少的设计功能

  3. 进一步阐述这两点:"身体" section通过命令行参数了解有关业务的 nothing 。实现命令行参数处理的代码几乎可以预先设置" as-is"到任何处理stdin的zsh脚本。此外,对命令行参数的处理方式(prepend vs append等)的更改会使注释# 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. 实现

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

2 个答案:

答案 0 :(得分:3)

除非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?

中同时阅读多个文件的文章

如果您打算两次阅读相同的输入,请参阅pipeBidirectional communication with yourself

答案 1 :(得分:1)

使用cat和一些常见的shell功能可能会更容易。 (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()
  • 无论输入量多大,此解决方案都不会出现死锁。