3
votes

I am trying to source a script from a Perl script (script.pl).

system ("source /some/generic/script");

Please note that this generic script could be a shell, python or any other script. Also, I cannot replicate the logic present inside this generic script into my Perl script. I tried replacing system with ``, exec, and qx//. Each time I got the following error:

Can't exec "source": No such file or directory at script.pl line 18.

I came across many forums on the internet, which discussed various reasons for this problem. But none of them provided a solution. Is there any way to run/execute source command from a Perl script?

3

3 Answers

4
votes

In bash, etc, source is a builtin that means read this file, and interpret it locally (a little like a #include).

In this context that makes no sense - you either need to remove source from the command and have a shebang (#!) line at the start of the shell script that tells the system which shell to use to execute that script, or you need to explicitly tell system which shell to use, e.g.

system "/bin/sh", "/some/generic/script";

[with no comment about whether it's actually appropriate to use system in this case].

2
votes

There are a few things going on here. First, a child process can't change the environment of its parent. That source would only last as long as its process is around.

Here's a short program that set and export an environment variable.

#!/bin/sh
echo "PID" $$
export HERE_I_AM="JH";

Running the file does not export the variable. The file runs in its own proces. The process IDs ($$) are different in set_stuff.sh and the shell:

$ chmod 755 set_stuff.sh
$ ./set_stuff.sh
PID 92799
$ echo $$
92077    
$ echo $HERE_I_AM       # empty

source is different. It reads the file and evaluates it in the shell. The process IDs are the same in set_stuff.sh and the shell, so the file is actually affecting its own process:

$ unset HERE_I_AM       # start over
$ source set_stuff.sh
PID 92077
$ echo $$
92077
$ echo $HERE_I_AM
JH

Now on to Perl. Calling system creates a child process (there's an exec in there somewhere) so that's not going to affect the Perl process.

$ perl -lwe 'system( "source set_stuff.sh;  echo \$HERE_I_AM" );
      print "From Perl ($$): $ENV{HERE_I_AM}"'
PID 92989
JH
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (92988):

Curiously, this works even though your version doesn't. I think the different is that in this there are no special shell metacharacters here, so it tries to exec the program directory, skipping the shell it just used for my more complicated string:

$ perl -lwe 'system( "source set_stuff.sh" ); print $ENV{HERE_I_AM}'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in print at -e line 1.

But, you don't want a single string in that case. The list form is more secure, but source isn't a file that anything can execute:

$ which source    # nothing
$ perl -lwe 'system( "source", "set_stuff.sh" ); print "From Perl ($$): $ENV{HERE_I_AM}"'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (93766):

That is, you can call source, but as something that invokes the shell.

Back to your problem. There are various ways to tackle this, but we need to get the output of the program. Instead of system, use backticks. That's a double-quoted context so I need to protect some literal $s that I want to pass as part of the shell commans

$ perl -lwe 'my $o = `echo \$\$ && source set_stuff.sh && echo \$HERE_I_AM`; print "$o\nFrom Perl ($$): $ENV{HERE_I_AM}"'
Use of uninitialized value in concatenation (.) or string at -e line 1.
93919
From Shell PID 93919
JH

From Perl (93918):

Inside the backticks, you get what you like. The shell program can see the variable. Once back in Perl, it can't. But, I have the output now. Let's get more fancy. Get rid of the PID stuff because I don't need to see that now:

#!/bin/sh
export HERE_I_AM="JH";

And the shell command creates some output that has the name and value:

$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; print $o'
HERE_I_AM=JH

I can parse that output and set variables in Perl. Now Perl has imported part of the environment of the shell program:

$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; for(split/\R/,$o){ my($k,$v)=split/=/; $ENV{$k}=$v }; print "From Perl: $ENV{HERE_I_AM}"'
From Perl: JH

Let's get the entire environment, though. env outputs every value in the way I just processed it:

$ perl -lwe 'my $o = `source set_stuff.sh && env | sort`; print $o'
...
DISPLAY=:0
EC2_PATH=/usr/local/ec2/ec2-api-tools
EDITOR=/usr/bin/vi
...

I have a few hundred varaibles set in the shell, and I don't want to expose most of them. Those are all set by the Perl process, so I can temporarily clear out %ENV:

$ perl -lwe 'local %ENV=(); my $o = `source set_stuff.sh && env | sort`; print $o'
HERE_I_AM=JH
PWD=/Users/brian/Desktop/test
SHLVL=1
_=/usr/bin/env

Put that together with the post processing code and you have a way to pass that information back up to the parent.

This is, by the way, similar to how you'd pass variables back up to a parent shell process. Since that output is already something the shell understands, you use the shell's eval instead of parsing it.

0
votes

You can't. source is a shell function that 'imports' the contents of that script into your current environment. It's not an executable.

You can replicate some of it's functionality by rolling your own - run or parse whatever you're 'sourcing' and capture the result:

 print `. file_to_source; echo $somevar`;

or similar.