How to use both pipes and prevent shell expansion in a perl system function?

If multiple arguments are passed to a perl system function, the shell extension won't work:

$ perl -e 'my $s="*"; system("echo", "$s" )'



If the command is passed as one argument, the extension will work:

$ perl -e 'my $s="echo *"; system("$s")'

Desktop Documents Downloads


The system function also allows multiple commands to be used and connected using pipes. This only works when the argument is passed as one command:

$ perl -e 'my $s="echo * | cat -n"; system("$s")'

1 Desktop Documents Downloads


How can I combine the above commands and use both pipes and prevent the shell from expanding?

I tried:

$ perl -e 'my $s="echo"; system("$s", "* | cat -n")'

* | cat -n


but it didn't work due to the reasons I described above (multiple arguments not expanded). As a result, I want:

1 *


EDIT: The problem I am facing is that when I use the following command:

system("echo \"$email_message\" | mailx -s \"$email_subject\" $recipient");


Then $ email_message is decrypted and it will split mailx if it contains some characters that are further expanded by the shell.


source to share

2 answers


has three calling conventions:


system($PROG, @ARGS)               # @ARGS>0

system( { $PROG } $NAME, @ARGS )   # @ARGS>=0


The first passes the command to the shell. This is equivalent to

system('/bin/sh', '-c', $SHELL_CMD)


The other two execute the program $PROG

. system

never prevents the shell from expanding or does any kind of escaping. There is simply no shell involved.

So your question is about creating a shell command. If you were on the command line, you can use

echo \* | cat -n



echo '*' | cat -n


transfer *

. You will need a function to do the acceleration task *

before interpolating it. Fortunately, there is already: String :: ShellQuote shell_quote


$ perl -e'
   use String::ShellQuote qw( shell_quote );
   my $s = "*";
   my $cmd1 = shell_quote("printf", q{%s\n}, $s);
   my $cmd2 = "cat -n";
   my $cmd = "$cmd1 | $cmd2";
   print("Executing <<$cmd>>\n");
Executing <<printf '%s\n' '*' | cat -n>>
     1  *


I used printf

instead echo

as it is very difficult to handle arguments starting with -

in echo

. Most programs accept --

to separate options from non-parameters, but not my echo


All of these complications raise the question: why are you shelling to send an email? It is usually much more difficult to handle errors from external programs than from libraries.



You can use open

to connect directly to mailx without your content being interpreted by the shell:

open( my $mail, "|-", "mailx", "-s", $email_subject, $recipient );
say $mail $email_message;
close $mail;


More details can be found in the open section of perlipc .



All Articles