在回答有关使用空格(以及可能的其他字符)安全转义文件名的this问题时,one of the answers表示使用Perl的内置 quotemeta 功能
quotemeta的文档说明:
quotemeta (and \Q ... \E ) are useful when interpolating strings
into regular expressions, because by default an interpolated variable
will be considered a mini-regular expression.
在quotemeta的文档中,唯一提及它的用法是使用/[A-Za-z_0-9]/
转义\
以外的所有字符,以便在正则表达式中使用。它没有说明文件名的用途。然而,这似乎是一种非常令人愉快的,如果没有记录的副作用。
在对SinanÜnüranswer对早期问题的评论中,hobbs说:
shell转义 与不同 regexp逃避,虽然我不能 想出一个情况 quotemeta会给人一种真正不安全的感觉 结果,它不适合任务。 如果你必须逃避,而不是 绕过壳,我建议尝试 String :: ShellQuote需要更多 使用sh单一的保守方法 报价除了以外的所有东西 单引号本身,和 单引号的反斜杠。 - hobbs 09年8月13日14:25
完全是否安全 - 使用quotemeta代替String::Shellquote这样更保守的文件引用? quotemeta utf8或多字节字符是否安全?
我把一个不明确的测试放在一起。除了包含\n
或\r
的文件名或目录名外,quotemeta似乎运作良好。虽然很少见,但这些字符在Unix中是合法的,我已经看过了。回想一下,某些字符,如LF,CR和NUL无法使用\
进行转义。我用带有quotemeta的700k文件读取了我的硬盘,但没有出现故障。
我怀疑(虽然我还没有证明),quotemeta可能会因多字节字符而失败,其中一个或多个字节属于ASCII范围。例如,à
可以编码为一个字符(UTF8 C3 A0)或两个字符(U + 0061给出a
u + 0300是一个组合格雷夫口音)。我在quotemeta中遇到的唯一失败是在我创建的路径中包含\n
或\r
的文件。我会对nasty_names
进行测试的其他角色感兴趣。
ShellQuote完全适用于所有文件名,但创建文件时由NUL终止的文件名除外。我从来没有遇到过它。
那么使用什么?需要明确的是:shell引用不是我经常做的事情,因为我通常只是使用Perl打开来打开进程的管道。该方法不会遇到讨论的shell问题。我感兴趣,因为我看到通常用于文件名转义的quotemeta。
(感谢Ether我添加了IPC :: System :: Simple)
测试文件:
use strict; use warnings; use autodie;
use String::ShellQuote;
use File::Find;
use File::Path;
use IPC::System::Simple 'capturex';
my @nasty_names;
my $top_dir = '/Users/andrew/bin/pipetestdir/testdir';
my $sub_dir = "easy_to_remove_me";
my (@qfail, @sfail, @ipcfail);
sub wanted {
if ($File::Find::name) {
my $rtr;
my $exec1="ls ".quotemeta($File::Find::name);
my $exec2="ls ".shell_quote($File::Find::name);
my @exec3= ("ls", $File::Find::name);
$rtr=`$exec1`;
push @qfail, "$exec1"
if $rtr=~/^\s*$/ ;
$rtr=`$exec2`;
push @sfail, "$exec2"
if $rtr=~/^\s*$/ ;
$rtr = capturex(@exec3);
push @ipcfail, \@exec3
if $rtr=~/^\s*$/ ;
}
}
chdir($top_dir) or die "$!";
mkdir "$top_dir/$sub_dir";
chdir "$top_dir/$sub_dir";
push @nasty_names, "name with new line \n in the middle";
push @nasty_names, "name with CR \r in the middle";
push @nasty_names, "name with tab\tright there";
push @nasty_names, "utf \x{0061}\x{0300} combining diacritic";
push @nasty_names, "utf e̋ alt combining diacritic";
push @nasty_names, "utf e\x{cc8b} alt combining diacritic";
push @nasty_names, "utf άέᾄ greek";
push @nasty_names, 'back\slashes\\Not\\\at\\\\end';
push @nasty_names, qw|back\slashes\\IS\\\at\\\\end\\\\|;
sub create_nasty_files {
for my $name (@nasty_names) {
open my $fh, '>', $name ;
close $fh;
}
}
for my $dir (@nasty_names) {
chdir("$top_dir/$sub_dir");
mkpath($dir);
chdir $dir;
create_nasty_files();
}
find(\&wanted, $top_dir);
print "\nquotemeta failed on:\n", join "\n", @qfail;
print "\nShell Quote failed on:\n", join "\n", @sfail;
print "\ncapturex failed on:\n", join "\n", @ipcfail;
print "\n\n\n",
"Remove \"$top_dir/$sub_dir\" before running again...\n\n";
答案 0 :(得分:15)
在这些假设下,Quotemeta是安全的:
无论你使用什么引用上下文,shell都违反规则2和3 - 在引号之外,反斜杠 - 换行不会生成换行符;在双引号中,反斜杠标点符号在输出中添加反斜杠(在某个标点符号列表之外);在单引号中,一切都是字面的,反斜杠甚至不能保护你免受结束单引号。
如果您需要引用shell的内容,我仍然建议String::ShellQuote
。我还建议尽量避免让shell完全处理您的文件名,如果可以,请使用LIST
- 表单system
/ exec
/ open
或IPC::Open2,{ {3}}或IPC::Open3。
除了shell之外的东西......许多不同的东西违反了一个或多个规则。例如,过时的POSIX“基本”正则表达式和各种编辑器正则表达式都有标点字符,默认情况下是非特殊的,但是当前面加反斜杠时变为特殊。基本上我所说的是,知道你正在非常好地提供数据,并正确逃脱。只有在quotemeta
完全合适的情况下才使用{{1}},或者如果您将其用于不太重要的事情。
答案 1 :(得分:3)
您还可以使用IPC::System::Simple capture()
或capturex()
(我在第一个问题的另一个答案中建议),这将让您绕过shell。
我将这些行添加到您的脚本中,发现没有示例失败:
use IPC::System::Simple 'capturex';
...
my (@qfail, @sfail, @ipcfail);
...
my @exec3= ("ls", $File::Find::name);
...
$rtr = capturex(@exec3);
push @ipcfail, \@exec3
if $rtr=~/^\s*$/ ;
...
print "\ncapturex failed on:\n", join "\n", @ipcfail;
但总的来说,你应该解决实际问题,而不是试图找到更好的创可贴。 quotemeta
专门用于转义正则表达式重要字符,正如您所发现的那样,它与对shell重要的字符集不完全重叠。
答案 2 :(得分:0)
以下是仅限Unix的解决方案;有关Windows支持,请参阅https://stackoverflow.com/a/32161361/45375。
另一种选择是这个简单的函数,即使使用非ASCII字符(假设编码正确),以及\n
和\r
也可以稳健地工作,但不包括NUL
(见下)。
sub quoteforsh { join ' ', map { "'" . s/'/'\\''/gr . "'" } @_ }
该函数用单引号括起每个参数,如果指定了多个参数,则用空格分隔它们。
使用单引号字符串,因为它们的内容不受类似POSIX的shell中的任何解释。
但是,因此,您甚至无法自行转义'
个实例,这需要以下解决方法:每个嵌入式'
实例都替换为'\''
(原文如此),这有效地分割了将字符串输入多个单引号字符串,并使用转义'
个实例 - \'
- 拼接 - 然后shell将字符串部分重新组合为单个字符串。
示例:
print quoteforsh 'I\'m here & wëll';
字面上产生(包括封闭的单引号)'I'\''m here & wëll'
,它们是 3 连续的字符串 - 'I'
,\'
和'&well'
,然后shell会重新组合成单个字符串,在删除引号后,会产生I'm here & wëll
。
OSX Unicode警告:HFS +将文件名存储在NFD中(分解 Unicode正常形式 - 基本字母后跟另一个与关联变音符号相关的字符),而Perl通常会创建NFC(组成 Unicode正常形式 - 单个字符标识重音字母。)
当使用 literal 文件名时,这种区别并不重要(系统调用执行映射),但是当使用globs时,它确实存在,并且不幸的是,你必须自己做两种形式之间的翻译。
支持NUL
(0x0)字符:
我不认为NUL
字符。在文件名中是一个现实世界的关注:
bash
,dash
,ksh
)忽略 NUL
个字符。在命令行上 - zsh
是唯一的例外。NUL
字符。在文件名中。此外,尝试将带有NUL
的文字传递给Perl的system()
函数会破坏调用,大概是因为字符串传递给{{1在第一个sh -c
:
NUL