在 Perl 中,执行反引号的默认 shell 是 sh。我想改用 bash,因为它的语法更丰富。到目前为止我发现建议的解决方案是
`bash -c \"echo a b\"`
明显的缺点是转义的双引号,这意味着我将很难在 bash 参数中使用双引号。例如,如果我想在 bash 中运行需要双引号的命令
echo "a'b"
上面的方法会很别扭。
Perl 的 system() 调用对此问题有一个解决方案:使用 ARRAY args,
system("bash", "-c", qq(echo "a'b"));
这使我原来的 bash 命令几乎始终保持不变。
我也想在反引号中使用 ARRAY args。可以吗?
一个,一个可以提交一份清单给
qx
;它被插入到一个字符串中,然后传递给 execvp
或 shell(参见 qx,以及 this post 的第二部分和评论)。如果您需要一个 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 命令,而不是通过一个令人费解的方法来正确转义并传递所有命令,例如 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(...)
运算符形式来演示另一种方式(对于包含单引号也很有用);对于这个简单的例子来说没有必要。还是要注意细节;这是使用复杂外部命令的本质,任何方法或工具都无法完全避免。
至于运行
bash
,请注意,通常 sh
委托给系统上的默认 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
是远古恶魔。使用现代工具/模块来运行外部命令怎么样?为了准备涉及的 bash 字符串,可能还需要做一些工作,但其他一切都会更好。选项比比皆是。例如
IPC::System::Simple 具有很少的实用功能
使用 Capture::Tiny 使用您喜欢的语法来包装
system
调用
IPC::Run 可以完成所有这些,甚至更多
为什么要使用 Perl(远远)丰富的外部命令?它是一种完整的、非常完整的编程语言,而不是具有一些编程功能的命令解释器。如果您需要 shell 的功能,为什么不通过 shell 只运行这些东西并在 Perl 中执行其他所有操作呢?
Capture::Tiny 是一个非常好的选择:正如概要所示,你可以这样做
use Capture::Tiny 'capture';
my ($output, $error_output, $exit_code) = capture {
system(@whatever);
};
以及使用内部系统
capture_stdout
如果您想要更简单的反引号行为。
此外,它非常通用,可以处理 Perl 代码(甚至是执行奇怪操作的 Perl 代码)以及外部程序,因此在您的工具箱中拥有它是一件好事。
我有以下可用的子程序
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
我的回答虽然啰嗦,但是满足了我的需要。我希望 Perl 有一个内置的解决方案,就像它处理 system() 调用的方式一样,我仍然希望如此。
给定
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;
您可以更改 perl 使用的 shell :
$ENV{PERL5SHELL} = "bash";
my $out = qx{echo "Hello ' world from bash \$BASH_VERSION"};
print($out);
您可以使用单引号作为分隔符与
qx
,如下所示:
my $out = qx'bash -c "echo a b"';
这将根据 perlop 保护命令免受 Perl 的双引号插值的影响。
不幸的是,这不适用于单引号。例如,如果您想做
echo "'"
,您需要以下内容:
my $out = `bash -c \"echo \\\"'\\\"\"`;
编辑:
为了帮助您管理引号的转义,您可以使用如下辅助函数:
use experimental qw(signatures);
sub bash_backticks($code) {
$code =~ s/'/'"'"'/g;
`bash -c '$code'`
}
如果安全等方面允许的话,对整个脚本使用临时文件怎么样?
例如,我需要使用以下 Perl 代码的过滤器脚本和数据来运行
jq
。代码是这样的
use File::Temp qw(tempfile);
my $BashCode = "jq -r '$JQCode' <<< '$JSON'";
# create a temp script file
my ($fh, $filename) = tempfile();
print $fh $BashCode;
close $fh;
# run the script
$out = `bash $filename`;
如您所知,当此命令行与
<<<
(带反引号)一起运行时,sh
会导致错误。而且 bash -c
在这种情况下并不容易起作用。所以,这就是解决方案。
您可以切换使用 bash,因为它的语法更丰富,无需额外转义。