首页 > 解决方案 > 使用多个参数调用 shell 命令

问题描述

我正在尝试通过 Perl 脚本自动创建证书。

我要运行的命令是:

easyrsa build-client-full $clientname nopass

我认为应该在 Perl 中完成的方式是:

   my $arguments = ("build-client-full $clientname nopass");       
   my $cmd = "$easyrsa_path/easyrsa"." "."$arguments";
   system("bash", $cmd);

然而,这会产生

“文件未找到”

在执行。我三次检查路径是否正确。

如果我这样尝试:

   my @arguments = ("bash", $easyrsa_path,"build-client-full $clientname nopass");
   system(@arguments);

重击回归

“未知命令'build-client-full test nopass'。不使用命令运行以获得使用帮助。”

标签: perl

解决方案


背景

当你使用system(LIST)whereLIST有多个元素时,Perl 不会调用 shell,而是直接调用 中的第一个元素给出的程序,并将列表的其余部分作为命令行参数逐字LIST传递,没有插值由外壳程序,包括空格上没有拆分参数。

因此,在您的第一个示例中,Perl 正在运行命令bash并传递字符串"$easyrsa_path/easyrsa build-client-full $clientname nopass",实际上是作为一个大的长参数,而在您的第二个示例中,它正在运行命令bash并传递两个参数$easyrsa_path"build-client-full $clientname nopass"。但是,我认为这easyrsa需要三个参数作为其参数列表中的单独字符串,shell 通常会拆分它们,但是由于您的两个调用system都没有使用 shell,所以它不起作用。

system(and exec) 有四种方式来解释他们的论点,根据文档:

  1. 如果您传递一个LIST不包含任何 shell 元字符的字符串(包括只有一个元素的 a),它会被拆分为单词并直接传递给execvp(3)(意味着它绕过了 shell)。 警告:此调用很容易与以下内容混淆 - 单个元字符将导致 shell 被调用,这可能很危险,尤其是当未检查的变量被插入到命令字符串中时。

  2. 如果您传递包含 shell 元字符的单个字符串(包括LIST只有一个元素的 a),则整个参数将传递给系统的命令 shell 进行解析。通常,这是/bin/sh -c在 Unix 平台上,但是“默认 shell”的想法是有问题的,并且肯定不能保证它会是bash(尽管它可能是)。

    警告:在此调用中system,您拥有 shell 的全部功能,这也意味着您有责任正确引用和转义任何 shell 元字符和/或空格。如果您明确需要shell 的功能,我建议您使用这种形式,否则,通常最好使用以下两种之一。

  3. 如果 in 中有多个参数,则使用 in 的参数LIST调用,这意味着避免使用 shell。(有关 Windows 的注意事项,请参见下文。)execvp(3)LIST

  4. 无论. _ system {EXPR} LIST _EXPR _ LIST(有关 Windows 的注意事项,请参见下文。)

如果您想传递 shell 通常会解释的特殊字符,则后两者是可取的,实际上我总是建议这样做,因为盲目地将用户输入传递给system会打开一个安全漏洞——我写了一篇关于这个的更长的文章在 PerlMonks 上

解决方案

@Borodin 和@AnFi 已经指出:如果您只是LIST正确地拆分了元素,它应该可以工作 - 看起来您不需要任何功能bash或任何外壳。并且不要忘记检查错误!

system("$easyrsa_path/easyrsa","build-client-full",$clientname,"nopass") == 0
    or warn "system failed: \$? = $?";

请注意,有一些很好的模块可以替代systemqx,我的首选模块通常是IPC::Run3. 如果您想从外部命令捕获输出,这些模块非常有用。在这种情况下,IPC::System::Simple可能会更容易,因为它提供了一个system更好的错误处理的替代品,systemx并且总是避免使用 shell。autodie(当您说. 时,该模块就是使用的use autodie ':all';。)

use IPC::System::Simple qw/systemx/;
systemx("$easyrsa_path/easyrsa","build-client-full",$clientname,"nopass");

请注意,如果您真的想打电话bash,您需要添加-c选项并说system("bash","-c","--","$easyrsa_path/easyrsa build-client-full $clientname nopass")。但正如我上面所说,我强烈建议不要这样做,因为如果$easyrsa_path$clientname包含任何 shell 元字符或恶意内容,您最终可能会遇到一个巨大的问题。

视窗

Windows 比上面的更复杂。文档说,避免调用 shell 的唯一“可靠”方法是system PROGRAM LIST表单,但在 Windows 上,命令行参数不是作为列表传递,而是作为单个大字符串传递,这取决于被调用的命令,而不是shell 来解释该字符串,不同的命令可能会以不同的方式执行此操作 -另请参阅. (不过,我听说过关于 的好消息Win32::ShellQuote。)

另外,还有perlport中记录的特殊system(1, @args)形式。


推荐阅读