如何安全地将带有空格的文件名传递给Perl中的外部命令?
我有一个处理大量文件名的Perl脚本,并在backticks中使用这些文件名。但文件名包含空格、撇号和其他时髦的字符 我希望能够正确地避开它们(即不使用头顶上的随机正则表达式)。是否有一个CPAN模块可以正确地转义字符串以用于bash命令?我知道我以前解决过这个问题,但这次我找不到任何关于它的东西。关于它的信息似乎少得出奇。你在找什么 返回EXPR的值,并将所有非“word”字符反斜杠 更新:正如霍布斯在评论中指出的那样,如何安全地将带有空格的文件名传递给Perl中的外部命令?,perl,escaping,Perl,Escaping,我有一个处理大量文件名的Perl脚本,并在backticks中使用这些文件名。但文件名包含空格、撇号和其他时髦的字符 我希望能够正确地避开它们(即不使用头顶上的随机正则表达式)。是否有一个CPAN模块可以正确地转义字符串以用于bash命令?我知道我以前解决过这个问题,但这次我找不到任何关于它的东西。关于它的信息似乎少得出奇。你在找什么 返回EXPR的值,并将所有非“word”字符反斜杠 更新:正如霍布斯在评论中指出的那样,quotemeta并非用于此目的,仔细考虑一下,嵌入式nuls可能存在问题
quotemeta
并非用于此目的,仔细考虑一下,嵌入式nul
s可能存在问题。另一方面,当遇到嵌入式null
s时,会发出咯咯声
最安全的方法是完全避开外壳。使用的列表形式可以实现这一目标(几个月前,我沮丧地发现,cmd.exe
可能仍然会涉及Windows),我建议这样做
如果您需要命令的输出,最好(安全方面)自己打开一个管道,如所示,如果您可以管理它(即,如果您直接调用某个命令,而不需要任何shell脚本或高级重定向把戏),最安全的做法是避免数据完全通过shell传递
在perl 5.8+中:
my @output_lines = do {
open my $fh, "-|", $command, @args or die "Failed spawning $command: $!";
<$fh>;
};
my@output\u lines=do{
打开我的$fh、“-|”、$command、@args或die“未能生成$command:$!”;
;
};
如果有必要支持5.6:
my @output_lines = do {
my $pid = open my $fh, "-|";
die "Couldn't fork: $!" unless defined $pid;
if (!$pid) {
exec $command, @args or die "Eek, exec failed: $!";
} else {
<$fh>; # This is the value of the C<do>
}
};
my@output\u lines=do{
my$pid=打开my$fh,“-|”;
die“无法分叉:$!”除非定义$pid;
如果(!$pid){
exec$命令,@args或die“Eek,exec失败:$!”;
}否则{
这是C的值
}
};
有关此类业务的更多信息,请参见perldoc-perlipc
,也可参见IPC::Open2
和IPC::Open3
tl;dr
以下子例程在类Unix和Windows系统上安全地引用(转义)文件名(路径)列表:
#!/usr/bin/env perl
sub quoteforshell {
return join ' ', map {
$^O eq 'MSWin32' ?
'"' . s/"/""/gr . '"'
:
"'" . s/'/'\\''/gr . "'"
} @_;
}
#'# Sample invocation
my $shellcmd = ($^O eq 'MSWin32' ? 'echo ' : 'printf "%s\n" ') .
quoteforshell('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
sub qxnoshell {
use IPC::Cmd;
return unless @_;
my @cmdargs = @_;
if ($^O eq 'MSWin32') { # Windows
# Ensure that the executable name ends in '.exe'
$cmdargs[0] .= '.exe' unless $cmdargs[0] =~ m/\.exe$/i;
unless (IPC::Cmd::can_run $cmdargs[0]) { # executable not found
# Issue warning, as qx// would and open '-|' below does.
my $warnmsg = "Executable '$cmdargs[0]' not found";
scalar(caller) eq 'main' ? warn($warnmsg . "\n") : warnings::warnif('exec', $warnmsg);
return;
}
for (@cmdargs[1..$#cmdargs]) {
if (m'"') {
s/"/\\"/; # \-escape embedded double-quotes
$_ = '"' . $_ . '"'; # enclose as a whole in embedded double-quotes
}
}
}
open my $fh, '-|', @cmdargs or return;
my @lines = <$fh>;
close $fh;
return wantarray ? @lines : join('', @lines);
}
示例(使用大多数POSIX外壳元字符):
请注意每个输入字符串是如何用单引号括起来的,以及所有输入字符串中唯一需要引号的字符是,
,如前所述,它被替换为,\'
这将按原样输出输入字符串,每行一个:
\foo/bar
I'm here
3" of snow
bar |&;()<>#!
这类似于上面的quoteforsh()
,除了
- 双引号用于将标记括起来,因为
不支持单引号cmd.exe
- 唯一需要转义的字符是
”,它被转义为“
——但是,请注意,对于文件名,这并不是严格必需的,因为Windows不允许在文件名中使用”
实例“
#!/usr/bin/env perl
sub quoteforshell {
return join ' ', map {
$^O eq 'MSWin32' ?
'"' . s/"/""/gr . '"'
:
"'" . s/'/'\\''/gr . "'"
} @_;
}
#'# Sample invocation
my $shellcmd = ($^O eq 'MSWin32' ? 'echo ' : 'printf "%s\n" ') .
quoteforshell('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
sub qxnoshell {
use IPC::Cmd;
return unless @_;
my @cmdargs = @_;
if ($^O eq 'MSWin32') { # Windows
# Ensure that the executable name ends in '.exe'
$cmdargs[0] .= '.exe' unless $cmdargs[0] =~ m/\.exe$/i;
unless (IPC::Cmd::can_run $cmdargs[0]) { # executable not found
# Issue warning, as qx// would and open '-|' below does.
my $warnmsg = "Executable '$cmdargs[0]' not found";
scalar(caller) eq 'main' ? warn($warnmsg . "\n") : warnings::warnif('exec', $warnmsg);
return;
}
for (@cmdargs[1..$#cmdargs]) {
if (m'"') {
s/"/\\"/; # \-escape embedded double-quotes
$_ = '"' . $_ . '"'; # enclose as a whole in embedded double-quotes
}
}
}
open my $fh, '-|', @cmdargs or return;
my @lines = <$fh>;
close $fh;
return wantarray ? @lines : join('', @lines);
}
- 不能禁止对现有环境变量引用的解释,例如
;相比之下,不存在的变量或孤立的%USERNAME%
实例就可以了。%
- 注意:您应该能够将
实例转义为%%
,但尽管这在批处理文件中有效,但在Perl中却无法正常工作:%%
投诉,例如关于未找到`perl“%%USERNAME%%.pl”`
,这意味着插入了%jdoe%.pl
,尽管字符数增加了一倍%USERNAME%%
- (另一方面,双引号字符串中孤立的
实例不需要像批处理文件中那样转义。)%
- 注意:您应该能够将
- 将嵌入式
实例转义为“
是唯一安全的方法,但这不是大多数目标程序所期望的。”
- 令人难以置信的是,在Windows上,所需的转义最终取决于目标程序-有关完整背景,请参阅
- 简言之,困境是:
- 如果您为目标程序转义,包括Perl在内的大多数程序,除了
”,那么部分参数 列表可能永远不会传递给目标程序,剩余部分可能会导致失败、不必要的文件重定向,或者更糟糕的是,意外执行任意命令\“
- 如果为
转义,可能会中断目标程序的解析cmd.exe
- 你不能两者都逃脱
- 如果您的命令根本不需要涉及shell,那么您可以解决这个问题—请参见下文
- 如果您为目标程序转义,包括Perl在内的大多数程序,除了
备选方案:无shell命令调用 如果您的命令是对单个可执行文件的调用,并且所有参数都要按原样传递,则根本不需要涉及shell,这:
- 不需要引用参数,这明显绕过了Windows上的
-引用问题“
- 通常效率更高
qx/
(`…`
)的无外壳替代程序,它接受命令作为要按原样解释的参数列表进行调用:
#!/usr/bin/env perl
sub quoteforshell {
return join ' ', map {
$^O eq 'MSWin32' ?
'"' . s/"/""/gr . '"'
:
"'" . s/'/'\\''/gr . "'"
} @_;
}
#'# Sample invocation
my $shellcmd = ($^O eq 'MSWin32' ? 'echo ' : 'printf "%s\n" ') .
quoteforshell('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
sub qxnoshell {
use IPC::Cmd;
return unless @_;
my @cmdargs = @_;
if ($^O eq 'MSWin32') { # Windows
# Ensure that the executable name ends in '.exe'
$cmdargs[0] .= '.exe' unless $cmdargs[0] =~ m/\.exe$/i;
unless (IPC::Cmd::can_run $cmdargs[0]) { # executable not found
# Issue warning, as qx// would and open '-|' below does.
my $warnmsg = "Executable '$cmdargs[0]' not found";
scalar(caller) eq 'main' ? warn($warnmsg . "\n") : warnings::warnif('exec', $warnmsg);
return;
}
for (@cmdargs[1..$#cmdargs]) {
if (m'"') {
s/"/\\"/; # \-escape embedded double-quotes
$_ = '"' . $_ . '"'; # enclose as a whole in embedded double-quotes
}
}
}
open my $fh, '-|', @cmdargs or return;
my @lines = <$fh>;
close $fh;
return wantarray ? @lines : join('', @lines);
}
- 由于使用了
,需要Perl v5.9.5+IPC::Cmd
- 请注意,子例程很难在Windows上运行:
- 即使参数作为列表传递,
在Windows上,如果初始调用尝试失败,仍然会使用open…,“-|”
——这同样适用于cmd.exe
和system()
exec()
# Unix: $out should receive literal '$$', which demonstrates that # /bin/sh is not involved. my $out = qxnoshell 'printf', '%s', '$$' # Windows: $out should receive literal '%USERNAME%', which demonstrates # that cmd.exe is not involved. my $out = qxnoshell 'perl', '-e', 'print "%USERNAME%"'
- 即使参数作为列表传递,