我有一个Perl脚本,它处理一堆文件名,并在反引号中使用这些文件名。但文件名包含空格,撇号和其他时髦字符。
我希望能够正确地逃脱它们(即不使用我头顶的随机正则表达式)。是否存在正确转义字符串以在bash命令中使用的CPAN模块?我知道我过去已经解决了这个问题,但这次我找不到任何东西。关于它的信息似乎很少。
答案 0 :(得分:6)
如果你可以管理它(即如果你直接调用某个命令,没有任何shell脚本或高级重定向恶作剧),最安全的做法是避免完全通过shell传递数据。
在perl 5.8 +中:
my @output_lines = do {
open my $fh, "-|", $command, @args or die "Failed spawning $command: $!";
<$fh>;
};
如果有必要支持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>
}
};
有关此类业务的详情,请参阅perldoc perlipc
,另请参阅IPC::Open2
和IPC::Open3
。
答案 1 :(得分:3)
您在寻找quotemeta吗?
返回EXPR的值,并将所有非“word”字符反斜杠。
更新:正如hobbs在评论中指出的那样,quotemeta
并非用于此目的,在考虑更多内容后,可能会遇到嵌入式nul
的问题秒。另一方面String::ShellQuote在遇到嵌入式null
时会发出嘶嘶声。
最安全的方法是完全避免使用shell。使用'system'的列表形式可以有很长的路要走(几个月前我发现cmd.exe
可能仍然涉及Windows)我感到沮丧,我建议这样做。
如果您需要输出命令,最好(安全地)自行打开管道,如hobbs' answer
所示答案 2 :(得分:2)
<强> TL;博士强>
以下子例程在类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`;
在类Unix系统上输出示例命令,显示所有输入参数都是通过未修改的方式传递的:
\foo/bar
I'm here
3" of snow
bar |&;()<>#!
在类Unix系统上,它应该适用于任何字符串(除了具有嵌入式NUL字符的字符串),而不仅仅是文件名 - 请参阅下面的详细信息。
在Windows上,嵌入式"
实例将转义为""
,这是唯一的安全方式,但遗憾的是,可能不是目标程序所期望的 - 详见下文;但请注意,如果您仅在Windows上传递文件名,则不需要考虑此问题,因为"
不是合法的文件名字符。
请参阅本文底部的 无shell 命令调用替代,以绕过Windows上的"
引用问题。
在类Unix平台,qx//
(`...`
的通用形式)和system
和exec
的单参数形式通过将命令传递给/bin/sh -c
来调用shell。假设/bin/sh
POSIX兼容(在给定系统上可能是也可能不是Bash)。
system
和exec
的单参数形式可能会或可能不会涉及shell - 他们根据传递的特定命令决定是否参与需要shell。例如,如果命令具有嵌入(文字)单引号或双引号,则调用shell 。由于下面的解决方案基于在命令字符串中嵌入单引号令牌,因此它也适用于system
和exec
的单参数形式。
在与POSIX兼容的shell中,您可以利用单引号字符串,它不会以任何方式插入其内容。
唯一的挑战是逃避单引号('
)本身,这需要欺骗,因为严格来说,不支持在单引号字符串中嵌入单引号由壳。
诀窍是用'
(原文如此)替换每个'\''
实例,这可以通过有效地将输入字符串拆分为多个来解决问题单引号字符串,带有转义'
个实例 - \'
- 拼接在中 - 然后shell将字符串部分重新组合为单个字符串。
这里有一个子程序,它取一个字符串列表(文件名),并返回一个空格分隔的字符串引用版本字符串,保证 literal 使用shell:
sub quoteforsh { join ' ', map { "'" . s/'/'\\''/gr . "'" } @_ }
示例(使用大多数POSIX shell元字符):
my $shellcmd = 'printf "%s\n" ' .
quoteforsh('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
这会将以下内容传递给/bin/sh -c
(此处显示为纯文字,不带任何引号):
printf "%s\n" '\foo/bar' 'I'\''m here' '3" of snow' 'bar |&;()<>#!'
请注意每个输入字符串是如何用单引号括起来的,以及所有输入字符串中唯一需要引用的字符是'
,如上所述,它被替换为'\''
。< / p>
这应该输出输入字符串 as-is ,每行一个:
\foo/bar
I'm here
3" of snow
bar |&;()<>#!
在 Windows 上,类似的子程序如下所示:
sub quoteforcmdexe { join ' ', map { '"' . s/"/""/gr . '"' } @_ }
这与上面的quoteforsh()
类似,除了
cmd.exe
不支持单引号。"
,其转义为""
- 请注意,对于文件名,这不是必须的,因为这不是必需的,因为Windows不允许文件名中有"
个实例。但是,有限制和陷阱:
%USERNAME%
;相比之下,不存在的变量或孤立的%
实例都可以。
%
实例转义%%
,但是当它在批处理文件中有效时,它无法解释为什么不能使用Perl :
`perl "%%USERNAME%%.pl"`
抱怨,例如,%jdoe%.pl
未被发现,暗示%USERNAME%
被插值,尽管%
字符加倍。%
个实例不需要转义它们在批处理文件中的方式。)"
转义嵌入式""
实例是唯一安全的方法,但并非大多数目标程序所期望的 。
\"
- 那么部分参数
list may 永远不会传递给目标程序,其余部分会导致失败,不必要的重定向到文件,或者更糟糕的是,意外执行任意命令。cmd.exe
转义,则可能会破坏目标程序的解析。替代方案:无shell 命令调用
如果您的命令是单个可执行文件的调用,并且所有参数都按原样传递,则根本不需要涉及shell ,其中:
"
引用问题以下子例程适用于类Unix系统和Windows ,并且是{strong>无shell替代qx//
({{1} }}),它接受命令作为参数列表调用来解释为:
`...`
<强>实施例强>
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);
}
# 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%"'
而需要Perl v5.9.5 +。IPC::Cmd
仍然会回到open ..., '-|'
- 这同样适用于cmd.exe
和system()
,顺便提一下。exec()
- 这会产生意想不到的后果 - 子例程(a)确保第一个列表参数是cmd.exe
可执行文件,(b)尝试定位它,以及(c)只有在可以找到可执行文件时才尝试调用该命令。*.exe
。