Both the backticks and qx are operators which act as the
readpipe function does. The documentation could do a better job of explaining it. The
perlop documentation briefly mentions that, as with the
system function, "if the string contains no shell metacharacters then it will be executed directly". As such, a better explanation can be found by reading the
documentation for system. Consider the following code.
What happens is that Perl scans the string and finds no shell metacharacters. Consequently, it acts somewhat similarly to a real shell in so far as it considers the string as whitespace separated words, before trying to execve(2) the first of those words, with the remaining words constituting the argument vector. Since there is no executable named "source" in PATH, the execve(2) syscall fails with ENOENT (which can be observed by proceeding to inspect the special $! variable).
While
readpipe can be coerced into always running
/bin/sh, going about it in this way is fundamentally incorrect. The reason is that the
sh executable should only ever be assumed to provide the features of the Shell Command Language, as defined by POSIX - and not necessarily all of the additional bells and whistles that the
bash shell offers. While Gentoo defaults to having
/bin/sh be a symlink to
bash, it can be changed, and there are many other distributions and platforms that have
sh as something else. What's more, if
bash finds itself being launched as
sh, it changes its behaviour so as to more strictly adhere to the specification. This can sometimes affect the behaviour of code that is specificially written for
bash.
To whit, if you need to
source shell code that is written for
bash, you should make a point of explicitly executing
bash itself. Here is one way of going about it in Perl that does not capture any output:
Code: Select all
# Because more than one arg is given, Perl will never execute sh
system('bash', '-c', 'source foo; # maybe do more stuff here ...');
If, on the other hand, you need to capture the standard output, here is how to do it using core Perl (that is, without any third party libraries/modules required):
Code: Select all
my @cmd = ('bash', '-c', 'source foo; # maybe do more stuff here ...');
open my $pipe, '-|', @cmd or die "Couldn't exec $cmd[0]: $!";
my @stdout = readline $pipe; # captures output lines to an array variable
close $pipe; # after closing, $? will equal 0 if $cmd[0] exited without error
The exec() function of PHP always executes
sh with the
-c operand. While this is sometimes useful, there are many occasions for which it is a nuisance, in which case directly executing a given program - not to mention capture its output - requires more labour in PHP than it does in Perl (for that, see
proc_open, whose interface was improved by the release of PHP 7.4).
All that being said, the last time I looked at the code of the
activate script generated for Python venvs, I found that the only thing it was doing that was of any great importance was to modify PATH. Well, one can just as easily do that in the execution environment of Perl itself! Below is an example.
Code: Select all
local $ENV{PATH} = "/myenv/bin:$ENV{PATH}";
my @stdout = readpipe('some-cool-bin-in-my-pyenv');
Indeed, the use of the local keyword there has it so that the change to PATH is only effective for the enclosing lexical scope. That can be useful if, say, calling into a subroutine and where it may be considered desirable for the changes to PATH to be effectively undone once the subroutine returns.
EDIT: Clarified a minor detail as to what happens when execve fails for Perl.