Perl:quotemeta是否仅用于正则表达式?文件名安全吗?

Perl:quotemeta是否仅用于正则表达式?文件名安全吗?,perl,file,unix,Perl,File,Unix,在回答关于使用空格(以及可能的其他字符)安全转义文件名的问题时,据说使用了Perl内置的函数 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. 在

在回答关于使用空格(以及可能的其他字符)安全转义文件名的问题时,据说使用了Perl内置的函数

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ür先前问题的评论中,霍布斯指出:

外壳逸出不同于 regexp正在逃跑,虽然我不能 想出一个情况 quotemeta会给出一个真正不安全的答案 结果,它不适合任务。 如果你必须逃跑,而不是 绕过外壳,我建议试试 字符串::ShellQuote,它需要更多 使用sh单参数的保守方法 引用来破坏一切,除了 单引号本身,以及 单引号的反斜杠霍布斯 2009年8月13日14:25

使用quotemeta代替像这样更保守的文件引用安全吗?quotemeta utf8或多字节字符安全吗

我做了一个不清楚的测试。quotemeta似乎工作得很好,除了文件名或目录名中带有
\n
\r
。虽然很少见,但这些字符在Unix中是合法的,我见过它们。回想一下,某些字符,如LF、CR和NUL不能用
\
转义。我用quotemeta读取硬盘上的700k文件,没有出现任何故障

我怀疑(尽管我还没有演示)quotemeta可能会在一个或多个字节属于ASCII范围的多字节字符中失败。例如,
a
可以编码为一个字符(UTF8 C3 A0)或两个字符(U+0061表示
a
U+0300表示组合graves重音)。我对quotemeta的唯一失败是我创建的路径中有
\n
\r
的文件。我对放入
讨厌的\u名称
进行测试的其他字符感兴趣

在创建文件时,除以NUL结尾的文件名外,其他文件名都可以完美地使用。我从来没有失败过

那么使用什么呢?需要明确的是:shell引用不是我经常做的事情,因为我通常只使用perlopen来打开一个进程的管道。这种方法不受讨论的shell问题的影响。我很感兴趣,因为我看到quotemeta经常用于文件名转义

(多亏了乙醚,我添加了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";
您还可以使用
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中重要的字符集不是完全重叠的。

quotemeta在以下假设下是安全的:

  • 只有非字母数字字符具有特殊含义
  • 如果非字母数字字符具有特殊含义,则在其前面加反斜杠将始终使其成为非特殊字符
  • 如果非字母数字字符没有特殊含义,则在其前面加反斜杠将毫无用处
  • 无论使用什么引号上下文,shell都违反规则2和3——在引号之外,反斜杠换行符不生成换行符;在双引号中,反斜杠标点符号将反斜杠放在输出中(在某个标点符号列表之外);在单引号中,所有内容都是文字,反斜杠甚至不能保护您免受单引号的影响

    如果您需要为shell引用内容,我仍然建议您使用
    String::ShellQuote
    。我还建议避免让shell完全处理您的文件名,如果可以的话,可以使用
    LIST
    -form
    system
    /
    exec
    /
    open

    至于外壳以外的东西。。。许多不同的事情违反了一条或多条规则。例如,过时的POSIX“basic”正则表达式和各种编辑器正则表达式都有标点符号,默认情况下标点符号不特殊,但在前面加反斜杠时会变得特殊。基本上,我想说的是,要非常清楚地知道你要将数据输入的对象,并正确地逃离。仅在完全适合的情况下使用
    quotemeta
    ,或者在不太重要的情况下使用它。

    以下是一种仅适用于Unix的解决方案;有关Windows支持,请参阅

    另一种选择是这个简单的函数,即使使用非ASCII字符(假设编码正确),它也能稳定地工作,
    \n
    \r
    ,但不包括
    NUL
    (见底部)

    函数用单引号括住每个参数,如果指定了多个参数,则用空格分隔

    之所以使用单引号字符串,是因为它们的内容在POSIX类shell中不受任何解释的约束

    但是,同样地,您甚至不能转义
    实例本身,这需要以下解决方法:每个嵌入的
    实例都替换为
    “\''
    (sic),这将有效地将输入字符串拆分为多个单引号字符串,将转义的
    '
    实例-
    \'
    -拼接到壳中,然后将字符串部分重新组装成单个字符串

    例如:

    print quoteforsh 'I\'m here & wëll';
    
    从字面上产生(包括随附的单引号)
    'I'\'m here&wëll'
    ,它
    print quoteforsh 'I\'m here & wëll';
    
    system "echo 'a\x{0}b'";  # BREAKS