Why open function doesn't interpret the option of my command every time? - perl

I want to use open function to run the following commands:
> echo "toto\ntata"
toto
tata
> echo -e "toto\ntata"
toto
tata
> echo -E "toto\ntata"
toto\ntata
> echo -n "toto\ntata"
toto\ntata
Note that for the last command, there is no trailing new line.
So I have the following script:
use strict;
sub run_cmd {
my ($cmd) = #_;
my $fcmd;
print("Run $cmd\n");
open($fcmd, "$cmd |");
while ( my $line = <$fcmd> ) {
print "-> $line";
}
close $fcmd;
}
eval{run_cmd('echo "toto\ntata"')};
eval{run_cmd('echo -e "toto\ntata"')};
eval{run_cmd('echo -E "toto\ntata"')};
eval{run_cmd('echo -n "toto\ntata"')};
But when I run it, I get this results:
Run echo "toto\ntata"
-> toto
-> tata
Run echo -e "toto\ntata"
-> -e toto
-> tata
Run echo -n "toto\ntata"
-> toto
-> tataRun echo -E "toto\ntata"
-> -E toto
-> tata
We can see the -n option is correctly interpreted by open, because there is no newline at the end of the text, but it is not the case for the options -e and -E. Worse, there are printed by echo.
Why my options are not interpreted by open every time? What should I do to get a correct output?

When you provide a shell command to system, qx, open, etc, it is treated as a sh shell command.
Here's the documentation for the echo builtin of dash which is used by some Linux distros as sh:
echo [-n] args...
Print the arguments on the standard output, separated by spaces. Unless the -n option is present, a newline is output following the arguments.
If any of the following sequences of characters is encountered during output, the sequence is not output. Instead, the specified action is performed:
...
\n Output a newline character.
...
It has no -e or -E option.
Running the commands from dash (aka sh in your case) results in exactly the same output as you received.
$ echo "toto\ntata"
toto
tata
$ echo -e "toto\ntata"
-e toto
tata
$ echo -E "toto\ntata"
-E toto
tata
$ echo -n "toto\ntata"
toto
tata$
Your test was probably run in the bash shell. The following will execute the command using bash instead:
open(my $pipe, "-|", "bash", "-c", $cmd)

Is there any reason why you use external program echo when perl has many ways to output some information to the console/file?
At your disposition you have print, printf, say
Perl input and output functions
Programming Perl

Related

xargs lines containing -e and -n processed differently

When running the following command with xargs (GNU findutils) 4.7.0
xargs -n1 <<<"-d -e -n -o"
I get this output
-d
-o
Why is -e and -n not present in the output?
From man xargs:
[...] and executes the command (default is /bin/echo) [...]
So it runs:
echo -d
echo -e
echo -n
echo -o
But from man echo:
-n do not output the trailing newline
-e enable interpretation of backslash escapes
And echo -n outputs nothing, and echo -e outputs one empty newlines that you see in the output.

What is the difference between "perl -n" and "perl -p"?

What is the difference between the perl -n and perl -p options?
What is a simple example to demonstrate the difference?
How do you decide which one to use?
How do you decide which one to use?
You use -p if you want to automatically print the contents of $_ at the end of each iteration of the implied while loop. You use -n if you don't want to print $_ automatically.
An example of -p. Adding line numbers to a file:
$ perl -pe '$_ = "$.: $_"' your_file.txt
An example of -n. A basic grep replacement.
$ perl -ne 'print if /some search text/' your_file.txt
-p is short for -np, and it causes $_ to be printed for each pass of the loop created by -n.
perl -ne'...'
executes the following program:
LINE: while (<>) {
...
}
while
perl -pe'...'
executes the following program:
LINE: while (<>) {
...
}
continue {
die "-p destination: $!\n" unless print $_;
}
See perlrun for documentation about perl's command-line options.
What is the difference between the perl -n and perl -p options?
-p causes each line to be printed; equivalent to:
while (<>) { ... } continue { print }
-n does not automatically print each line; equivalent to:
while(<>) {...}
What is a simple example to demonstrate the difference?
e.g., replace foo with FOO:
$ echo 'foo bar' | perl -pe 's/foo/FOO/'
FOO bar
$ echo 'foo bar' | perl -ne 's/foo/FOO/'
$
How do you decide which one to use?
One example where -n is useful is when you don't want every line printed, and there is a conditional print in the code, e.g., only show lines containing foo:
$ echo -e 'foo\nbar\nanother foo' | perl -ne 'print if /foo/;'
foo
another foo
$
The command-line options are documented in perlrun documentation
perl -n is equivalent to while(<>){...}
perl -p is equivalent to while(<>){...;print;}

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

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.
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.
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.

How to tell if my program is being piped to another (Perl)

"ls" behaves differently when its output is being piped:
> ls ???
bar foo
> ls ??? | cat
bar
foo
How does it know, and how would I do this in Perl?
In Perl, the -t file test operator indicates whether a filehandle
(including STDIN) is connected to a terminal.
There is also the -p test operator to indicate whether a filehandle
is attached to a pipe.
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:1, pipe:0
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN' < /tmp/foo
term:0, pipe:0
$ echo foo | perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:0, pipe:1
File test operator documentation at perldoc -f -X.
use IO::Interactive qw(is_interactive);
is_interactive() or warn "Being piped\n";

System command in perl

I need to run a system command which would go to a directory and delete sub directories excluding files if present. I wrote the below command to perform this operation:
system("cd /home/faizan/test/cache ; for i in *\; do if [ -d \"$i\" ]\; then echo \$i fi done");
The command above keeps throwing syntax error. I have tried multiple combinations but still not clear how this should go. Please suggest.
Well, your command line does contain syntax errors. Try this:
system("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
Or better yet, only loop over directories in the first place;
system("for i in /home/faizan/test/cache/*/.; do echo \$i; done");
Or better yet, do it without a loop:
system("echo /home/faizan/test/cache/*/.");
(I suppose you will want to rmdir instead of echo once it is properly debugged.)
Or better yet, do it all in Perl. There is nothing here which requires system().
You're still best off trying this as a bash command first. Formatting that properly makes it much clearer that you're missing statement terminators:
for i in *; do
if [ -d "$i" ]; then
echo $i
fi
done
And condensing that by replacing new lines with semicolons (apart from after do/then):
for i in *; do if [ -d "$i" ]; then echo $i; fi; done
Or as has been mentioned, just do it in Perl (I haven't tested this to the point of actually uncommenting remove_tree - be careful!):
use strict;
use warnings;
use File::Path 'remove_tree';
use feature 'say';
chdir '/tmp';
opendir my $cache, '.';
while (my $item = readdir($cache)) {
if ($item !~ /^\.\.?$/ && -d $item) {
say "Deleting '$item'...";
# remove_tree($item);
}
}
Using system
my #args = ("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
system(#args);
Using Subroutine
sub do_stuff {
my #args = ( "bash", "-c", shift );
system(#args);
}
do_stuff("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
As question title stand for system command, this will answer directly, but the sample command using bash contain only thing that will be simplier in perl only (take a look at other answer using opendir and -d in perl).
If you want to use system (instead of open $cmdHandle,"bash -c ... |"), the prefered syntax for execution commands like system or exec, is to let perl parsing the command line.
Try this (as you've already done):
perl -e 'system("bash -c \"echo hello world\"")'
hello world
perl -e 'system "bash -c \"echo hello world\"";'
hello world
And now better, same but letting perl ensure command line parsing, try this:
perl -e 'system "bash","-c","echo hello world";'
hello world
There are clearly 3 argument of system command:
bash
-c
the script
or little more:
perl -e 'system "bash","-c","echo hello world;date +\"Now it is %T\";";'
hello world
Now it is 11:43:44
as you can see in last purpose, there is no double double-quotes enclosing bash script part of command line.
**Nota: on command line, using perl -e '...' or perl -e "...", it's a little heavy to play with quotes and double-quotes. In a script, you could mix them:
system 'bash','-c','for ((i=10;i--;));do printf "Number: %2d\n" $i;done';
or even:
system 'bash','-c','for ((i=10;i--;));do'."\n".
'printf "Number: %2d\n" $i'."\n".
'done';
Using dots . for concatening part of (script part) string, there are always 3 arguments.