Calling a shell command with multiple arguments

给你一囗甜甜゛ 提交于 2021-02-04 21:07:34

问题


I'm trying to automate creating certificates via a Perl script.

The command I want to run is:

easyrsa build-client-full $clientname nopass

The way I thought it should be done in Perl is:

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

However, this yields

"file not found"

on execution. I triple checked that the path is correct.

If I try it like this:

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

Bash returns

"Unknown command 'build-client-full test nopass'. Run without commands for usage help."


回答1:


Background

When you use system(LIST) where LIST has more than one element, Perl will not call the shell, and instead directly invoke the program given by the first element in the LIST, and use the rest of the list as command line arguments to be passed verbatim, with no interpolation by the shell, including no splitting arguments on whitespace.

So in your first example, Perl is running the command bash and passing the string "$easyrsa_path/easyrsa build-client-full $clientname nopass", literally as one big long argument, and in your second example, it's running the command bash and passing the two arguments $easyrsa_path and "build-client-full $clientname nopass". However, I assume that easyrsa needs the three arguments as separate strings in its argument list, which the shell would normally split, but since both of your calls to system aren't using the shell, it's not working.

system (and exec) have four ways of interpreting their arguments, as per the documentation:

  1. If you pass a single string (including a LIST with only one element) that does not contain any shell metacharacters, it is split into words and passed directly to execvp(3) (meaning it bypasses the shell). Warning: This invocation is easily confused with the following - a single metacharacter will cause the shell to be invoked, which can be dangerous especially when unchecked variables are interpolated into the command string.

  2. If you pass a single string (including a LIST with only one element) that does contain shell metacharacters, the entire argument is passed to the system's command shell for parsing. Normally, that's /bin/sh -c on Unix platforms, but the idea of the "default shell" is problematic, and there is certainly no guarantee that it'll be bash (though it could be).

    Warning: In this invocation of system, you have the full power of the shell, which also means you're responsible for correctly quoting and escaping any shell metacharacters and/or whitespace. I recommend you only use this form if you explicitly want the power of the shell, and otherwise, it's usually best to use one of the following two.

  3. If there is more than one argument in LIST, this calls execvp(3) with the arguments in LIST, meaning the shell is avoided. (See below for caveats on Windows.)

  4. The form system {EXPR} LIST always runs the program named by EXPR and avoids the shell, no matter what's in LIST. (See below for caveats on Windows.)

The latter two are desirable if you want to pass special characters that the shell would normally interpret, and I'd actually always recommend doing this, since blindly passing user input into system can open up a security hole - I wrote a longer article about that over on PerlMonks.

Solutions

@Borodin and @AnFi have already pointed out: If you simply split up the elements of the LIST properly, it should work - it doesn't look like you need any features of bash or any shell here. And don't forget to check for errors!

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

Note that there are good modules that provide alternatives to system and qx, my go-to module is usually IPC::Run3. These modules are very helpful if you want to capture output from the external command. In this case, IPC::System::Simple might be easier since it provides a drop-in replacement for system with better error handling, as well as systemx which always avoids the shell. (That module is what autodie uses when you say use autodie ':all';.)

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

Note that if you really wanted to call bash, you'd need to add the -c option and say system("bash","-c","--","$easyrsa_path/easyrsa build-client-full $clientname nopass"). But as I a said above, I strongly recommend against this, since if $easyrsa_path or $clientname contain any shell metacharacters or malicious content, you may end up having a huge problem.

Windows

Windows is more complicated than the above. The documentation says that the only "reliable" way to avoid calling the shell there is the system PROGRAM LIST form, but on Windows, command line arguments are not passed as a list, but a single big string, and it's up to the called command, not the shell, to interpret that string, and different commands may do that differently - see also. (I have heard good things about Win32::ShellQuote, though.)

Plus, there's the special system(1, @args) form documented in perlport.




回答2:


If you pass multiple parameters to system then each one forms a separate parameter to the command line. So it is as though you had entered

easyrsa "build-client-full test nopass"

and you correctly get the error

Unknown command 'build-client-full test nopass'

You also don't need to add bash: perl will run the shell for you if necessary

You can either pass the whole command to system

system($cmd)

and perl will pass it to the shell to be processed as if you'd entered it at the command prompt. Or you can split the parameters properly

system("$easyrsa_path/easyrsa", "build-client-full", $clientname, "nopass")

which will make perl call easyrsa directly unless the command contains things that need the shell to process, like output redirection



来源:https://stackoverflow.com/questions/50553031/calling-a-shell-command-with-multiple-arguments

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!