-1

If multiple arguments are passed to perl's system function then the shell expansion will not work:

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

# RESULT
*

If the command is passed as an one argument then the expansion will work:

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

# RESULT
Desktop Documents Downloads

The system function also allows to using multiple commands and connect them using pipes. This only works when argument is passed as an one command:

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

# RESULT
1 Desktop Documents Downloads

How can I combine mentioned commands and use both pipes and prevent shell expansion?

I have tried:

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

# RESULT
* | cat -n

but this did not work because of reasons that I've described above (multiple arguments are not expanded). The result that I want is:

1 *

EDIT: The problem that I'm actually facing is that when I use following command:

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

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

Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 1
    Why are you not using single quotes then? `system("echo '$email' ...` Besides that, when you're using double quotes, it also seems more logical to use single quotes than to backslash a double quote. – TLP Apr 12 '15 at 22:52
  • 1
    @TLP, Because that fails if `$email` contains single quotes. It's not a bad idea to check for the NUL character either. `shell_quote` handles all that. – ikegami Apr 13 '15 at 12:21

2 Answers2

5

system has three calling conventions:

system($SHELL_CMD)

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

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

The first passes a command to the shell. It's equivalent to

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

The other two execute the program $PROG. system never prevents shell expansion or performs any escaping. There's simply no shell involved.

So your question is about building a shell command. If you were at the prompt, you might use

echo \* | cat -n

or

echo '*' | cat -n

to pass *. You need a function that performs the job of escaping * before interpolating it. Fortunately, one already exists: String::ShellQuote's 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");
   system($cmd);
'
Executing <<printf '%s\n' '*' | cat -n>>
     1  *

I used printf instead of echo since it's very hard to handle arguments starting with - in echo. Most programs accept -- to separate options from non-options, but not my echo.

All these complications beg the question: Why are you shelling out to send an email? It's usually much harder to handle errors from external programs than from libraries.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thank you for reply. I have not experience with String::ShellQuote but as you said it can fail under some conditions and it seems like overkill to use external module for me. Furthermore I cannot use anything 3rd party in my script. Why I am using external shell for sending emails? Because it works from command line and I thought it will work also from perl. – Wakan Tanka Apr 13 '15 at 19:36
  • Re "Furthermore I cannot use anything 3rd party in my script", huh, don't you realize anyone giving you an answer is a third party? – ikegami Apr 13 '15 at 19:37
  • I mean 3rd party module or something like that. Maybe one of following may be the way but do not know http://stackoverflow.com/questions/506275/how-can-i-open-pipes-with-oo-style – Wakan Tanka Apr 13 '15 at 19:39
  • So my code on CPAN is from a third party, but my code on SO isn't? How does that make any sense???? – ikegami Apr 13 '15 at 19:41
3

You can use open to pipe 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 open section of perlipc.

Loonis
  • 56
  • 3
  • This seems to be the best solution, but unfortunately I have to deal with old version of perl (v5.6.1) where `perl -V | grep useperlio` returns `useperlio=undef` which means I cannot use your advice, as it is described here http://perldoc.perl.org/functions/open.html I am still getting the error message: `Can't use an undefined value as filehandle reference at ...` I've found similar problem here http://stackoverflow.com/questions/4202196/cant-use-an-undefined-value-as-filehandle-reference-error-in-perl – Wakan Tanka Apr 13 '15 at 19:34
  • From http://perldoc.perl.org/functions/open.html `You can see whether your Perl was built with PerlIO by running perl -V and looking for the useperlio= line. If useperlio is define , you have PerlIO; otherwise you don't.` – Wakan Tanka Apr 13 '15 at 19:40
  • I don't have an older perl to play with right now, but you be able to change the syntax a bit: `open( FH, "| mailx -s $email_subject $recipient" )` – Loonis Apr 14 '15 at 00:10