使用 bash 的 Perl 反引号

Perl backticks using bash

在 Perl 中,执行反引号的默认 shell 是 sh。我想改用 bash 以获得更丰富的语法。到目前为止,我发现建议的解决方案是

`bash -c \"echo a b\"`

明显的缺点是转义双引号,这意味着我将很难在 bash args 中使用双引号。例如,如果我想 运行 命令在 bash

中需要双引号
echo "a'b"

上面的方法会很别扭

Perl 的 system() 调用有一个解决这个问题的方法:使用 ARRAY args,

system("bash", "-c", qq(echo "a'b"));

这使我原来的 bash 命令保持不变,而且几乎总是如此。

我也想在反引号中使用 ARRAY args。可能吗?

您可以像这样在 qx 中使用单引号作为分隔符:

my $out = qx'bash -c "echo a b"';

这将根据 perlop 保护命令免受 Perl 的 double-quote 插值。

不幸的是,这对单引号不起作用。例如,如果您想执行 echo "'",则需要以下内容:

my $out = `bash -c \"echo \\"'\\"\"`;

编辑:

为了帮助您管理引号的转义,您可以使用这样的辅助函数:

use experimental qw(signatures);
sub bash_backticks($code) {
    $code =~ s/'/'"'"'/g;
    `bash -c '$code'`
}

您可以更改 perl 使用的 shell :

$ENV{PERL5SHELL} = "bash";
my $out = qx{echo "Hello ' world from bash $BASH_VERSION"};
print($out);

我有以下可用的子

    sub bash_output {
       my ($cmd) = @_; 
    
       open my $ifh, "-|", "bash", "-c", $cmd or die "cannot open file handler: $!";
    
       my $output = ""; 
       while (<$ifh>) {
          $output .= $_; 
       }   
    
       close $ifh;
    
       return $output;
    }

    print "test bash_output()\n";

    my @strings = (
         qq(echo "a'b"),
         'echo $BASH_VERSION',
         '[[ "abcde" =~ bcd ]] && echo matched',
         'i=1; ((i++)); echo $i',
   );

   for my $s (@strings) {
         print "bash_output($s) = ", bash_output($s), "\n";
   }

输出为

bash_output(echo "a'b") = a'b

bash_output(echo $BASH_VERSION) = 4.4.20(1)-release

bash_output([[ "abcde" =~ bcd ]] && echo matched) = matched

bash_output(i=1; ((i++)); echo $i) = 2

我的答案是long-winded,但它满足了我的需要。我希望 Perl 有一个 built-in 解决方案,就像它处理 system() 调用的方式一样,我仍然希望如此。

一个,一个可以提交一个列表给qx;它被插入到一个字符串中,然后传递给 execvp 或 shell(参见 qx, and the second part of 和注释)。如果你 需要 一个 shell 那么大概那个字符串包含 shell 元字符所以它通过 shell.

my @cmd = qw(ls -l "dir with spaces");
#my @cmd = qw(ls -l "dir with spaces" > outfile);
my @out = qx(@cmd);
print for @out;

我创建了一个 "dir with spaces" 目录,里面有一个文件来测试。 (对于其中包含引号的命令,确实会使用 shell。)

接下来,我原则上会推荐一个模块来编写那些 shell 命令,而不是通过 nail-biter 来正确转义并传递所有命令,例如 String::ShellQuote

use String::ShellQuote qw(shell_quote); 

my @cmd = ('ls', '-l', q(dir with spaces)); 

my $quoted = shell_quote(@cmd);; 
my @out = qx($quoted); 
#my @out = qx($quoted > outfile); 
print for @out;

我使用单引号的q(...)运算符形式来演示另一种方式(对于包含单引号也很有用);这个简单的例子没有必要。人们仍然需要注意细节;这是使用复杂外部命令的本质,无法通过任何方法或工具完全避免。

至于 运行ning bash,请注意通常 sh 在您的系统上委托给 default-of-sorts shell,在许多系统上它是bash 即用。但是如果它不在你的身上,在命令中使用 bash -c 的一种方法是首先准备命令,然后将其添加到 qx 字符串

my @cmd = ('ls', '-l', q(dir with spaces)); 
my $quoted = shell_quote(@cmd); 
my @out = qx(bash -c "$quoted"); 
#my @out = qx(bash -c "$quoted" > outfile); 
print for @out;

我想提供更多注意事项:

  • 那个qx是上古恶魔。将现代 tools/modules 用于 运行ning 外部命令怎么样?为了准备您所涉及的 bash 字符串,可能还有一些工作要做,但其他一切都会更好。选择比比皆是。例如

  • 为什么要使用外部命令,而 Perl 的(远远)更丰富?它是一种完整的、非常完整的编程语言,与具有一些编程功能的 command-interpreter 相比。如果你需要 shell 的功能,为什么不 运行 通过 shell 做那些事情,而在 Perl 中做所有其他事情?

Capture::Tiny 是一个很好的选择:如 SYNOPSIS 所示,您可以这样做

use Capture::Tiny 'capture';
my ($output, $error_output, $exit_code) = capture {
    system(@whatever);
};

如果您想要更简单的反引号行为,也可以在 capture_stdout 中使用系统。

此外,它非常 general-purpose,可以处理 Perl 代码(甚至是做奇怪事情的 Perl 代码)以及外部程序,所以在您的工具箱中拥有它是一件好事。

给出

my @cmd = ( "bash", "-c", qq(echo "a'b") );

您可以使用以下任何一项:

use Shell::Quote qw( shell_quote );

my $cmd = shell_quote( @cmd );
my $output = `$cmd`;
die "Can't spawn child: $!\n"                    if $? == -1;
die "Child killed by signal ".( $? & 0x7F )."\n" if $? & 0x7F;
die "Child exited with error ".( $? >> 8 )."\n"  if $? >> 8;

use IPC::System::Simple qw( capturex );

my $output = capturex( @cmd );

use IPC::Run qw( run );

run \@cmd, '>', \my $output;
die "Child killed by signal ".( $? & 0x7F )."\n" if $? & 0x7F;
die "Child exited with error ".( $? >> 8 )."\n"  if $? >> 8;