许多开始程序员编写代码这样的:

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

  `cp $from $to`;
}

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

有帮助吗?

解决方案

有几个人已经提到,只应使用反引号的时候:

  • 你需要捕捉(或抑制)的输出。
  • 不存在内在功能或Perl模块做同样的任务,或者你有一个很好的理由不利用该模块或建筑。
  • 你消毒你的输入。
  • 你检查返回的价值。

不幸的是,事情就像检查返回的价值 正确 可以是很具有挑战性。没有死亡的一个信号?没有运行,以完成,但返回一个有趣的出口状况?标准的方式尝试解释 $? 只是可怕的。

我建议你使用 IPC::System::简单 模块的 capture()system() 职能,而不是反引号。的 capture() 能工作就像反引号,但下列除外:

  • 它提供详细的诊断,如果该命令没有开始,被杀害的一个信号,或者返回一个意想不到的出口价值。
  • 它提供详细的诊断,如果传递受污染的数据。
  • 它提供了一个简单的机制,用于指定接受出口价值观。
  • 它允许你打电话反引号中没有外壳,如果你想要的。
  • 它提供了可靠的机制对于避免壳,即使如果你使用一个单一的论点。

该命令还工作一直在操作系统和Perl版本,不同于Perl's built-in system() 这可能不检查污染数据时称,与多个参数上旧版本的Perl(例如,5.6.0与多个参数),或其中可以叫外壳无论如何在窗。

作为例子,下列代码段将节省的成果的呼叫 perldoc 进入一个标,避免了外壳,并引发一个例外,如果页上找不到(由于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: $0 arguments\n";

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

IPC::System::简单 是纯Perl,适用于5.6.0及以上的,并没有任何依赖性,通常不会跟你Perl分布。(On Windows它取决于一个Win32::模块,附有两ActiveState和草莓Perl).

免责声明:我是作者 IPC::System::简单, 所以我可能会出现一些偏见。

其他提示

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

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

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

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

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

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

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

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

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

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

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

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

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

来自“perlop”手册页:

  

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

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

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

my $user = `/bin/whoami`;

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

只说“whoami”或“cp”或“cp”。如果用户的路径发生变化,则存在意外运行除预期命令之外的命令的风险 - 这是恶意攻击者可能试图利用的安全漏洞。

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

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

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

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

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

  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脚本中使用时,只是正确的工具。

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

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

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

Backticks适合业余爱好者。防弹解决方案是“安全管道打开”。 (参见“man perlipc”)。你在另一个进程中执行你的命令,这允许你首先使用STDERR,setuid等进行futz。优点:它不依赖于shell来解析@ARGV,而不是open(“$ cmd $” args |&quot;),这是不可靠的。您可以重定向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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top