什么时候是在正确的时间(和错误的时间)使用反引号?
许多新手程序员都是这样编写代码的:
sub copy_file ($$) { my $from = shift; my $to = shift; `cp $from $to`; }
这不好吗,为什么?应该使用反引号吗?如果是这样,怎么办?
解决方案
仅当我们需要捕获命令的输出时,才应使用反引号。否则,应使用system()。而且,当然,如果有执行此工作的Perl函数或者CPAN模块,则应使用它而不是两者中的任何一个。
无论哪种情况,都强烈建议两件事:
首先,清理所有输入:如果代码暴露给可能的不受信任的输入,请使用"污染"模式(-T)。即使不是,也要确保处理(或者防止)诸如空格或者三种引号之类的时髦字符。
其次,检查返回码以确保命令成功。这是如何执行此操作的示例:
my $cmd = "./do_something.sh foo bar"; my $output = `$cmd`; if ($?) { die "Error running [$cmd]"; }
示例很糟糕,因为有perl内置的插件可以做到这一点,这些插件是可移植的,并且通常比反引号替代方案更有效。
仅在没有Perl内置(或者模块)替代产品时才应使用它们。这既适用于反引号,也适用于system()调用。反引号旨在捕获执行的命令的输出。
当我们想从命令中收集输出时,请使用反引号。
否则," system()"是一个更好的选择,尤其是在不需要调用Shell处理元字符或者命令解析的情况下。我们可以通过将列表传递给system()来避免这种情况,例如system('cp','foo','bar')
(不过,我们最好为该特定示例使用模块:))
在Perl中,总是有不止一种方法来做我们想做的任何事情。反引号的主要目的是将shell命令的标准输出转换为Perl变量。 (在示例中,cp命令打印的所有内容都将返回给调用方。)在示例中使用反引号的不利之处在于,我们无需检查shell命令的返回值;而是,无需检查shell命令的返回值。 cp可能会失败,并且我们不会注意到。我们可以将其与特殊的Perl变量$?一起使用。当我想执行一个shell命令时,我倾向于使用system:
system("cp $from $to") == 0 or die "Unable to copy $from to $to!";
(还要注意,这对于带有嵌入式空格的文件名将失败,但是我想这不是问题的重点。)
这是一个反引号可能有用的人为示例:
my $user = `whoami`; chomp $user; print "Hello, $user!\n";
对于更复杂的情况,我们还可以将open用作管道:
open WHO, "who|" or die "who failed"; while(<WHO>) { # Do something with each line } close WHO;
仅当我们要捕获输出时才应使用反引号。在这里使用它们"看起来很愚蠢"。它会提示任何查看代码的人,使我们发现我们对Perl不太熟悉。
如果要捕获输出,请使用反引号。
如果要运行命令,请使用系统。我们将获得的一项优势是能够检查退货状态。
尽可能使用模块以实现可移植性。在这种情况下,File :: Copy可以满足要求。
规则很简单:如果可以找到内建的引号来执行相同的工作,或者如果引号是CPAN上的强大模块,它将为我们完成,则不要使用反引号。反引号通常依赖于不可移植的代码,即使我们不确定变量,我们仍然可以敞开自己的大门,面对很多安全漏洞。
除非我们非常严格地指定允许的内容(不要禁止的内容,否则我们会错过一切),切勿对用户数据使用反引号!这是非常非常危险的。
通常,最好使用系统而不是反引号,因为:
- 系统鼓励调用者检查命令的返回码。
- 系统允许使用"间接对象"表示法,这样更安全并增加了灵活性。
- 反引号在文化上与Shell脚本相关联,这在代码读者中可能并不常见。
- 反引号对可能是很繁琐的命令使用最少的语法。
用户可能倾向于使用反引号而不是系统的原因之一是对用户隐藏STDOUT。通过重定向STDOUT流,可以更轻松,灵活地完成此操作:
my $cmd = 'command > /dev/null'; system($cmd) == 0 or die "system $cmd failed: $?"
此外,轻松实现摆脱STDERR的目的:
my $cmd = 'command 2> error_file.txt > /dev/null';
在需要使用反引号的情况下,我更喜欢使用qx {}来强调出现了一个重量级命令。
另一方面,拥有另一种方法可以真正。有时,我们只需要查看命令打印到STDOUT的内容即可。反引号(当在shell脚本中使用时)恰好是完成此任务的合适工具。
从" perlop"联机帮助页中:
That doesn't mean you should go out of your way to avoid backticks when they're the right way to get something done. Perl was made to be a glue language, and one of the things it glues together is commands. Just understand what you're getting yourself into.
捕获stdout的另一种方法(除了pid和退出代码外)是使用IPC :: Open3可能会否定使用系统和反引号。
对于我们正在显示的情况,使用File :: Copy模块可能是最好的。但是,为回答问题,每当我需要运行系统命令时,通常都依赖IPC :: Run3. 它提供了很多功能,例如收集返回码以及标准和错误输出。
无论执行什么操作,以及清理输入并检查代码的返回值,请确保使用其显式完整路径调用任何外部程序。例如说
my $user = `/bin/whoami`;
或者
my $result = `/bin/cp $from $to`;
如果用户的路径发生更改,则仅说" whoami"或者" cp"会冒意外运行除我们预期之外的命令的风险,这是恶意攻击者可能试图利用的安全漏洞。
已经有一些人提到我们仅在以下情况下使用反引号:
- 我们需要捕获(或者抑制)输出。
- 没有内置函数或者Perl模块可以执行相同任务,或者我们有充分的理由不使用该模块或者内置函数。
- 我们可以清理输入内容。
- 我们检查返回值。
不幸的是,诸如正确检查返回值之类的挑战可能非常艰巨。它死于信号吗?它是否运行完毕,但返回了有趣的退出状态?试图解释$?
的标准方法简直糟透了。
我建议使用IPC :: System :: Simple模块的capture()
和system()
函数,而不要使用反引号。 capture()
函数与反引号一样工作,除了:
- 如果命令没有启动,被信号杀死或者返回意外的退出值,它将提供详细的诊断信息。
- 如果传递了受污染的数据,它将提供详细的诊断。
- 它提供了一种简单的机制来指定可接受的退出值。
- 如果需要,它允许我们在不使用Shell的情况下调用反引号。
- 它提供了避免外壳的可靠机制,即使我们使用单个参数也是如此。
与Perl的内置system()
不同,在较旧版本的Perl上调用多个参数时(例如,带有多个参数的5.6.0),这些命令也可以在操作系统和Perl版本之间一致地工作,或者在Windows下仍然可以调用Shell。
例如,以下代码片段将对perldoc的调用结果保存到标量中,避开了shell,并在找不到页面时引发异常(因为perldoc返回1)。
#!/usr/bin/perl -w use strict; use IPC::System::Simple qw(capture); # Make sure we're called with command-line arguments. @ARGV or die "Usage:sub run_cmd { my $cmd = shift @_; my @args = @_; my $fh; # file handle my $pid = open($fh, '-|'); defined($pid) or die "Could not fork"; if ($pid == 0) { open STDERR, '>/dev/null'; # setuid() if necessary exec ($cmd, @args) or exit 1; } wait; # may want to time out here? if ($? >> 8) { die "Error running $cmd: [$?]"; } while (<$fh>) { # Have fun with the output of $cmd } close $fh; }arguments\n"; my $documentation = capture('perldoc', @ARGV);
IPC :: System :: Simple是纯Perl,可在5.6.0及更高版本上运行,并且没有任何Perl发行版通常不会附带的依赖项。 (在Windows上,它取决于ActiveState和Strawberry Perl随附的Win32 ::模块)。
免责声明:我是IPC :: System :: Simple的作者,因此我可能会表现出一些偏见。
反引号适合业余爱好者。防弹解决方案是"安全管道打开"(请参阅" man perlipc")。我们可以在另一个进程中执行命令,这使我们可以先用STDERR,setuid等执行命令。优点:与open(" $ cmd $ args |")不同,它不依靠shell来解析@ARGV。 。我们可以重定向STDERR并更改用户特权,而无需更改主程序的行为。这比反引号更冗长,但是我们可以将其包装在自己的函数中,例如run_cmd($ cmd,@ args);。
##代码##