Pierre
Pierre

Reputation: 2090

How can I keep my environment variables between two calls to open?

I have a perl code running on Windows. This code calls open twice to execute two different batch files. The first one set an environment variable and the second one needs to use it. Unfortunately, the value sets to the variable is lost between the two calls.

Here is my Perl code.

my $hdl;
open($hdl, "set.bat |");
while(my $line = <$hdl>) {
    print("$line\n");
}
close($hdl);

open($hdl, "get.bat |");
while(my $line = <$hdl>) {
    print("$line\n");
}
close($hdl);

My set.bat file which sets the environment variable:

set VAR=20
echo %VAR%

And my get.bat which uses the environment variable:

echo %VAR%

When I run the perl code, here is the results:

>perl my_code.pl
>set VAR=20
>echo 20
20
>echo
ECHO is on.

We can see set.bat correctly sets the value of VAR but get.bat is unable to use it.


Also, if I run my two batch scripts in a row from the cmd prompt, I have the result I expect:

>set.bat
>set VAR=20
>echo 20
20
>get.bat
>echo 20
20

What can I do to let my second batch script to use my environment variable in my perl code?

Upvotes: 4

Views: 1228

Answers (3)

zdim
zdim

Reputation: 66899

The open with a pipe creates a new process and then your set.bat runs in that process and sets that environment variable in that process, which then exits.

Then get.bat runs in a yet different process and can see nothing of the first process.

However, they both inherit the environment of the process in which your Perl script is running. So you can set the needed environment in the script (via %ENVfor instance) and then create a subprocess, and then that subprocess sees the environment.

On the other hand, you can run both shell scripts in the same subprocess, if that fits your purposes, via system for instance. Then one can export a variable into the environment, and after it's source-ed the next script to run will see it. Here is an example in Linux (can't do Windows right now).

A command-line program ("one-liner")

perl -we'system("/bin/bash", "-c", q(source set.bat.sh; get.bat.sh))'

with the file set.bat.sh

#!/bin/bash
VAR=20
export VAR

and file get.bat.sh

#!/bin/bash
echo $VAR

The one-liner prints one line with 20.


The pipe-open is normally said to fork a process (see open), but on Windows that can only be emulated (via threads) as there is no native fork; see perlfork. However, perlport indicates that on Windows the pipe-open does create a subprocess (via Win32 API).


A Linux example (can't test in Windows right now)

perl -we'$v = qx("set.bat.sh"); chomp $v; $ENV{VAR} = $v; system("get.bat.sh")'

with set.bat.sh file

#!/bin/bash
VAR=20
echo $VAR

and get.bat.sh

#!/bin/bash
echo $VAR

Since a subprocess cannot directly change the environment of its parent the set.bat.sh prints $VAR to STDOUT and then its parent, the perl script, can read it from that stream (captured by qx) and set it in its own environment, which its next (grand-)child get.bat.sh inherits. (A system forks a process, and in it another one is forked to run get.bat.sh. Environment is passed down.)

The weakness of this, of course, is that the perl script needs to know the name of the variable, VAR. An improvement then would be for the set.bat to send the name itself along with the value.

Upvotes: 6

tripleee
tripleee

Reputation: 189638

The subprocesses are isolated from each other, and generally cannot affect the environments of each other or of the parent process.

For this limited example, maybe it is sufficient to reimplement part of the CMD language in Perl. If you only need to support a small subset, such as, say, assigning static strings to variables, it's not too hard:

if ($line ~ /^set\s+(\w+)=(\w+)$/) {
    $ENV{$1} = $2;
}

Perhaps use a different hash than ENV if you don't absolutely need to propagate these values to the environment.

Upvotes: 2

Daniel Heinrich
Daniel Heinrich

Reputation: 800

That's not easily done. The reason is, that a child process normally inherits a copy of the parents process environment. Including environment variables.

See: How to Share ENV Variables Among Perl Scripts

Upvotes: 2

Related Questions