perl - 使用多个参数调用 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'。不使用命令运行以获得使用帮助。”
解决方案
背景
当你使用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
) 有四种方式来解释他们的论点,根据文档:
如果您传递一个
LIST
不包含任何 shell 元字符的字符串(包括只有一个元素的 a),它会被拆分为单词并直接传递给execvp(3)
(意味着它绕过了 shell)。 警告:此调用很容易与以下内容混淆 - 单个元字符将导致 shell 被调用,这可能很危险,尤其是当未检查的变量被插入到命令字符串中时。如果您传递包含 shell 元字符的单个字符串(包括
LIST
只有一个元素的 a),则整个参数将传递给系统的命令 shell 进行解析。通常,这是/bin/sh -c
在 Unix 平台上,但是“默认 shell”的想法是有问题的,并且肯定不能保证它会是bash
(尽管它可能是)。警告:在此调用中
system
,您拥有 shell 的全部功能,这也意味着您有责任正确引用和转义任何 shell 元字符和/或空格。如果您明确需要shell 的功能,我建议您只使用这种形式,否则,通常最好使用以下两种之一。如果 in 中有多个参数,则使用 in 的参数
LIST
调用,这意味着避免使用 shell。(有关 Windows 的注意事项,请参见下文。)execvp(3)
LIST
无论. _
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: \$? = $?";
请注意,有一些很好的模块可以替代system
和qx
,我的首选模块通常是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)
形式。
推荐阅读
- node.js - 传入的 typescript 参数必须是 12 个字节的单个字符串或 24 个十六进制字符的字符串
- sql - 如何根据“datediff”创建列
- python - 为什么即使设置 sys.setswitchinterval(1000) 也会有多个线程工作
- docker - Docker 与 Apache、PHP 和 Elasticsearch
- javascript - 如何从上下文 API 中的组件调用函数?
- python - 程序有什么问题
- android - Android Room 从 SQLiteOpenHelper 迁移
- node.js - fspromises.writeFile() 在 process.exit() 上写入空文件
- mysql - 从 n:n 关系中选择一个值
- python - SHAP 解释器识别错误的框架