I am attempting to use Perl5 to fork()
a child process. The child process should exec()
another program, redirecting its STDIN
to a named pipe, and STDOUT
and STDERR
to log files. The parent process continues running in a loop, using waitpid
and checking $?
to restart the child in case it dies with non-zero exit status.
Perl documentation for the exec()
function says:
If there is more than one argument in LIST, this calls execvp(3) with the arguments in LIST. If there is only one element in LIST, the argument is checked for shell metacharacters, and if there are any, the entire argument is passed to the system's command shell for parsing (this is
/bin/sh -c
on Unix platforms, but varies on other platforms). If there are no shell metacharacters in the argument, it is split into words and passed directly toexecvp
, which is more efficient. Examples:exec '/bin/echo', 'Your arguments are: ', @ARGV; exec "sort $outfile | uniq";
This sounds pretty cool, and I'd like to run my external program without an intermediary shell, as shown in these examples. Unfortunately, I am unable to combine this with output redirection (as in /bin/foo > /tmp/stdout
).
In other words, this does not work:
exec ( '/bin/ls', '/etc', '>/tmp/stdout' );
So, my question is: how do I redirect the STD*
files for my sub-command, without using the shell?
Redirection via <
and >
is a shell feature, which is why it does not work in this usage. You are essentially calling /bin/ls
and passing >/tmp/stdout
as just another argument, which is easily visible when replacing the command by echo
:
exec ('/bin/echo', '/etc', '>/tmp/stdout');
prints:
/etc >/tmp/stdout
Normally, your shell (/bin/sh
) would have parsed the command, spotted the redirection attempts, opened the proper files, and also pruned the argument list going in to /bin/echo
.
However -
A program started with exec()
(or system()
) will inherit the STDIN
, STDOUT
and STDERR
files of its calling process. So, the proper way to handle this is to
exec()
to start the program.Rewriting your example code above, this works fine:
close STDOUT;
open (STDOUT, '>', '/tmp/stdout');
exec ('/bin/ls', '/etc');
...or, using the indirect-object syntax recommended by perldoc
:
close STDOUT;
open (STDOUT, '>', '/tmp/stdout');
exec { '/bin/ls' } ('ls', '/etc');
(in fact, according to the documentation, this final syntax is the only reliable way to avoid instantiating a shell in Windows.)