Using Bash Environment Variables from Within a Perl Script

Using Bash environment variables from within a Perl script?

There are two queries here, on use of Bash variables and on running external commands.

There is the %ENV hash in Perl, with environment variables

perl -wE'say $ENV{PWD}'

However, you are often better off getting the equivalent within the script, as things may have a subtly different meaning for the script or change as the script runs.

More importantly, using shell commands exposes you to all kinds of potential problems with quoting, shell injection, and interpretation. For instance, the command you show is dangerous, as outlined in Charles Duffy comment. It is in principle better to use Perl's rich functionality. See for example

  • Executing system commands safely while coding in Perl   and

  • Using system commands in Perl instead of built in
    libraries/functions

for a sober, and detailed, account of advantages.


In case you do need to run external commands, it is best to avoid the shell altogether, for example by using the multi-argument form of system. If you need the output of the command as well there are various modules in Perl that provide that. See links below.

If you also need to use the shell's capabilities, instead of quoting everything just right in order for the shell to receive what it needs better use a ready tool like String::ShellQuote.

Some examples:

  • How to use both pipes and prevent shell expansion in perl system function?

  • Perl is respecting '<' as a regular character rather an output redirection

  • How to pipe the content of a variable as STDIN in a qx{} statement in Perl?

  • Perl system command with multiple parameters output to file.

Note that qx operator (backticks) uses /bin/sh, which may or may not get relegated to Bash. So if you want Bash you'll need system('/bin/bash', '-c', @cmd). See the links with examples.


Here is a full example related to the objective behind the question.

Your program's working directory may be other than expected depending on how it's started. For one, it changes after chdir. I don't know your exact intent with PWD, but in Perl there are core Cwd::cwd and FindBin with $RealBin, for the current working directory and for the directory where the script resides (generally different things).

To create a symbolic link to $path, with the relative path following the current working directory

use warnings;
use strict;
use Cwd qw(cwd);

my $cwd = cwd;

my $path = '/first/path';

symlink($path, "$cwd/second/path") or die "Can't make a symlink: $!";

If the path is meant to be the script's location use $RealBin from FindBin instead of cwd.

Note that with symlink you cannot pass a directory instead of a link name. See this page.

Perl set and get env in different bash script

The child process can inherit the parent's environment but cannot make any changes. Similarly the parent cannot have access to the child's environment as well. Hence to catch environment of the child in parent the child should print the values as shown in the bellow code. The below code will set already existing environment variables as well, but this can be optimized

# perlscript.pl

my $env_val = `. setnameenv.sh; env`;
my @env_list = split "\n", $env_str;

foreach (@env_list)
{
/([\w_]+)=(.*)/;
$ENV{$1} = $2;
}

print `. getnameenv.sh`;

find the actual explanation in this SO answer

Perl script, how to source an environment variables from .bash_profile


  • The easiest way would be to set the environment variables within perl with $ENV{"name"}=.... These variables then will be propagated automatically to any programs started from within the perl script, no matter if perl or shell scripts
  • alternatively you could open the .bash_profile, parse it within perl, extract the variables and then set them again within perl with $ENV. This is error prone because there might be several ways to declare the variables.
  • or you could spawn a shell, which reads the .bash_profile and calls env afterwards. Then you read and parse the output from this shell. This is similar to the previous proposal, but the output you have to parse is more clearly defined.
  • or you could use a shell script which sources .bash_profile and then spawns the perl script.

How could I access shell variable from Perl script?

From bash manual:

When a program is invoked it is given an array of strings called the
environment. [...] The shell provides several ways to manipulate the
environment. On invocation, the shell scans its own environment and
creates a parameter for each name found, automatically marking it for
export to child processes. Executed commands inherit the environment.
The export and declare -x commands allow parameters and functions to
be added to and deleted from the environment.

So (assuming a Bash shell) using:

export LINES

will make the variable $LINES available from within a Perl script startet from the Shell (using $ENV{LINES} from the Perl script).

How to reload Bash environment variables inside Perl script?


#! /usr/bin/env perl

use strict;
use warnings;

BEGIN {
if (!exists( $ENV{PERL5LIB} )) {
if (!@ARGV || $ARGV[0] ne '--no-restart') {
exec('bash', '-lc', '"$@"', $0, $^X, $0, '--no-restart', @ARGV);
}
}

if (@ARGV && $ARGV[0] eq '--no-restart') {
shift(@ARGV);
}
}

...

(Obviously, using Getopt::Long or some-such would be better.)

This not only meets your spec, but provides a bypass if needed.

That said, the whole concept is flawed. Most people shouldn't be using PERL5LIB, and it's not your job to fix badly configured systems (those that need PERL5LIB but don't have it set when it should be set). Instead, fix your environment.

using environment variable in the perl script

$GATE in a double quoted string will be considered a Perl variable. If you want to use an environment variable, you can use the %ENV hash:

system ("bsub xyz +OPTIONS_GATE=$ENV{GATE}")

Alternatively, you can escape the dollar sign so Perl does not treat $GATE as a Perl variable:

system ("bsub xyz +OPTIONS_GATE=\$GATE")

Or use a single quoted string, which does not interpolate variables:

system ('bsub xyz +OPTIONS_GATE=$GATE')

Note that if you had used

use strict;
use warnings;

It would have told you about this error. strict would have said:

Global symbol "$GATE" requires explicit package name
Execution of script.pl aborted due to compilation errors.

And warnings would have said:

Name "main::GATE" used only once: possible typo at script.pl line 12.
Use of uninitialized value $GATE in string at script.pl line 12.

When you do not use use strict; use warnings; your errors are not removed, they are only hidden from you, so that they are harder to find. Therefore, always use these two pragmas.

how to source a shell script [environment variables] in perl script without forking a subshell?

Child environments cannot change parent environments. Your best bet is to parse env.sh from inside the Perl code and set the variables in %ENV:

#!/usr/bin/perl

use strict;
use warnings;

sub source {
my $name = shift;

open my $fh, "<", $name
or die "could not open $name: $!";

while (<$fh>) {
chomp;
my ($k, $v) = split /=/, $_, 2;
$v =~ s/^(['"])(.*)\1/$2/; #' fix highlighter
$v =~ s/\$([a-zA-Z]\w*)/$ENV{$1}/g;
$v =~ s/`(.*?)`/`$1`/ge; #dangerous
$ENV{$k} = $v;
}
}

source "env.sh";

for my $k (qw/foo bar baz quux/) {
print "$k => $ENV{$k}\n";
}

Given

foo=5
bar=10
baz="$foo$bar"
quux=`date +%Y%m%d`

it prints

foo => 5
bar => 10
baz => 510
quux => 20110726

The code can only handle simple files (for instance, it doesn't handle if statements or foo=$(date)). If you need something more complex, then writing a wrapper for your Perl script that sources env.sh first is the right way to go (it is also probably the right way to go in the first place).

Another reason to source env.sh before executing the Perl script is that setting the environment variables in Perl may happen too late for modules that are expecting to see them.

In the file foo:

#!/bin/bash

source env.sh

exec foo.real

where foo.real is your Perl script.



Related Topics



Leave a reply



Submit