什么时候(和错误的时间)使用反引号?

时间:2008-09-22 16:12:30

标签: perl

许多初级程序员编写如下代码:

sub copy_file ($$) {
  my $from = shift;
  my $to = shift;

  `cp $from $to`;
}

这是不好的,为什么?是否应该使用反引号?如果是这样,怎么样?

14 个答案:

答案 0 :(得分:22)

有些人已经提到过你应该只在以下情况下使用反引号:

  • 您需要捕获(或抑制)输出。
  • 没有内置函数或Perl模块执行相同的任务,或者您有充分的理由不使用该模块或内置。
  • 您清理输入。
  • 您检查返回值。

不幸的是,正确检查返回值 等事情可能非常具有挑战性。它死于信号吗?它是否已完成,但返回一个有趣的退出状态?尝试解释$?的标准方法很糟糕。

我建议使用IPC::System::Simple模块的capture()system()函数,而不是反引号。 capture()函数就像反引号一样,除了:

  • 如果命令未启动,被信号杀死或返回意外的退出值,它会提供详细的诊断。
  • 如果传递了污染数据,它会提供详细的诊断。
  • 它提供了一种指定可接受退出值的简便机制。
  • 如果你愿意,它允许你在没有shell的情况下调用反引号。
  • 即使您使用单个参数,它也提供了避免shell的可靠机制。

这些命令在操作系统和Perl版本中也可以一致地工作,这与Perl的内置system()不同,后者在旧版本的Perl上使用多个参数调用时可能无法检查污染数据(例如,多个5.6.0)参数),或者可以在Windows下调用shell。

例如,以下代码片段会将对perldoc的调用结果保存到标量中,避免使用shell,并在找不到页面时抛出异常(因为perldoc返回1)。 / p>

#!/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: $0 arguments\n";

my $documentation = capture('perldoc', @ARGV);

IPC::System::Simple是纯粹的Perl,适用于5.6.0及更高版本,并且没有任何通常不会随Perl发行版一起提供的依赖项。 (在Windows上,它取决于ActiveState和Strawberry Perl附带的Win32 ::模块)。

免责声明:我是IPC::System::Simple的作者,所以我可能会有偏见。

答案 1 :(得分:11)

规则很简单:如果你能找到一个内置工作来做同样的工作,或者如果它们是CPAN上的一个强大的模块可以为你做,那么永远不要使用反引号。反引号通常依赖于不可移植的代码,即使你解开变量,你仍然可以打开很多安全漏洞。

从不使用用户数据的反引号,除非你已经非常严格地指定了什么是允许的(不是什么是不允许的 - 你会错过的东西)!这非常非常危险。

答案 2 :(得分:6)

当且仅当您需要捕获命令的输出时,才应使用

反引号。否则,应使用system()。当然,如果有一个Perl函数或CPAN模块来完成这项工作,那么应该使用它来代替它们。

在任何一种情况下,都强烈鼓励两件事:

首先,清理所有输入:如果代码暴露给可能不受信任的输入,请使用污点模式(-T)。即使不是,也要确保处理(或防止)像空格或三种引用这样的时髦字符。

其次,检查返回代码以确保命令成功。以下是如何执行此操作的示例:

my $cmd = "./do_something.sh foo bar";
my $output = `$cmd`;

if ($?) {
   die "Error running [$cmd]";
}

答案 3 :(得分:5)

如果要从命令中收集输出,请使用反引号。

否则system()是更好的选择,特别是如果您不需要调用shell来处理元字符或命令解析。您可以通过将列表传递给system()来避免这种情况,例如system('cp', 'foo', 'bar')(但是您可能最好为特定的示例使用模块:))

答案 4 :(得分:5)

捕获stdout的另一种方法(除了pid和退出代码)是使用IPC::Open3可能否定系统和反引号的使用。

答案 5 :(得分:4)

在Perl中,总有不止一种方法可以做任何你想要的事情。反引号的主要目的是将shell命令的标准输出转换为Perl变量。 (在您的示例中,cp命令打印的任何内容都将返回给调用者。)在示例中使用反引号的缺点是您不检查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 WHO, "who|"
    or die "who failed";
while(<WHO>) {
    # Do something with each line
}
close WHO;

答案 6 :(得分:3)

来自“perlop”联机帮助页:

  

这并不意味着你应该走出去   你的方式避免反击时   他们是获得某些东西的正确方法   完成。 Perl被制成胶水   语言,以及其中的一件事   胶合在一起就是命令。只是   了解你得到了什么   你自己。

答案 7 :(得分:3)

对于使用File::Copy模块显示的情况,最好。但是,要回答您的问题,每当我需要运行系统命令时,我通常都依赖IPC::Run3。它提供了许多功能,例如收集返回代码以及标准和错误输出。

答案 8 :(得分:2)

无论您做什么,以及清理输入并检查代码的返回值,请确保使用明确的完整路径调用任何外部程序。例如说

my $user = `/bin/whoami`;

my $result = `/bin/cp $from $to`;

如果用户的路径发生变化,那么只说“whoami”或“cp”就会冒着意外运行命令而非预期命令的风险 - 这是恶意攻击者可能试图利用的安全漏洞。

答案 9 :(得分:1)

你的例子很糟糕,因为perl内置的内容是可移植的,通常比反引号更有效。

只有在没有Perl内置(或模块)替代方案时才应该使用它们。这是用于反引号和system()调用。反引号用于捕获已执行命令的输出。

答案 10 :(得分:1)

只有在想要捕获输出时才会使用反引号。在这里使用它们“看起来很傻”。它会让任何看你的代码的人都知道你对Perl不是很熟悉。

如果要捕获输出,请使用反引号。 如果要运行命令,请使用系统。您将获得的一个优势是能够检查返回状态。 尽可能使用模块以实现可移植性。在这种情况下,File :: Copy适合账单。

答案 11 :(得分:1)

一般情况下,最好使用系统而不是反引号,因为:

  1. system 鼓励调用者检查命令的返回码。

  2. 系统允许使用“间接对象”表示法,这种表示法更安全,并且增加了灵活性。

  3. 反引号在文化上与shell脚本有关,这在代码的读者中可能并不常见。

  4. 反引号使用最少的语法来处理繁重的命令。

  5. 用户可能会使用反引号代替系统的一个原因是隐藏用户的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脚本中使用时,只是正确的工具。

答案 12 :(得分:0)

Perl具有分裂的个性。一方面,它是一种很棒的脚本语言,可以取代shell的使用。在这种一次性的I-watching-the-results使用中,反引号很方便。

使用编程语言时,应避免使用反引号。这是一个缺乏错误 检查,如果单独的程序反引号执行可以避免,效率是 获得。

除上述内容外,在未使用命令输出时应使用系统函数。

答案 13 :(得分:0)

Backticks适合业余爱好者。防弹解决方案是“安全管道打开”(参见“man perlipc”)。你在另一个进程中执行你的命令,这允许你先使用STDERR,setuid等进行futz。优点:它不依赖于shell来解析@ARGV,而不是open(“$ cmd $ args” |“),这是不可靠的。您可以重定向STDERR并更改用户权限,而无需更改主程序的行为。这比反引号更冗长,但你可以将它包装在你自己的函数中,如run_cmd($ cmd,@ args);


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;
}