http://www.perlmonks.org?node_id=1186708


in reply to Re: Improve pipe open? (redirect hook)
in thread Improve pipe open?

I don't see a need for a hook between fork and exec hidden in system and open, and abusing %SIG for that hook makes it even worse. That hook introduces action-at-a-distance.

If you intend to make some stuff happen between fork and exec, write it explicitly:

sub redirected_system { my @args=@_; my $pid=fork() // die "Can't fork: $!"; if ($pid) { # (parent) waitpid($pid,0); # plus whatever is needed to collect data from the child } else { # (child) # modify file handles as needed (redirection) # change UID, GID if needed # chdir if needed # chroot if needed exec { $args[0] } @args or die "exec failed: $!"; } }

This is clean, readable, and has no action-at-a-distance. Of course, it is possible to use a generic function to allow changes to the child process without resorting to global variables:

sub hooked_system(&@) { my ($hook,@args)=@; my $pid=fork() // die "Can't fork: $!"; if ($pid) { # (parent) waitpid($pid,0); # plus whatever is needed to collect data from the child } else { # (child) $hook->(); exec { $args[0] } @args or die "exec failed: $!"; } }

I also don't see how a hook would solve the problem of perl invoking the default shell in case of three-argument pipe open or single-argument system. And please don't tell me that the hook code should start guessing how the single string should be splitted into a list of arguments. This is excactly what perl already does: It guesses, and if the string looks too complex to guess (see below), it delegates that to a random default shell. This is the cause of the trouble, not its solution.

qx, ``, system and pipe open are already wrappers for fork and exec. Adding more and more parameters will give us nightmares like CreateProcessAsUser(), effectively passing more than 30 parameters plus command line arguments to a nightmare function that will finally start a child process. See also Re^3: Set env variables as part of a command.


Perl guessing

exec states:

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 to execvp, which is more efficient.

So, what exactly are shell metacharacters? I don't know. My guess is that a lot of the non-alpanumeric, printable ASCII characters count as shell metacharacters. They may depend on the operating system, too. And they may have changed over time. It seems that Perl_do_exec3() in doio.c of the perl 5.24.1 sources contains at least a little bit of the guessing logic. And it seems that the word "exec" at the start of the string also forces perl to invoke the default shell, not only non-alphanumeric characters. To make things even worse, some of the logic depends on the preprocessor symbol CSH. My guess is that happens only if the default shell is a csh variant.

BTW: A trailing 2>&1 seems to be handled by perl as a special case, without resorting to the default shell.

Alexander

--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)