Perl: import shell variable - perl

In shell, I had sourced .cshrc file which contains some defined variables like user name.
I need to pass these variables to a certain Perl script.
For example in shell terminal, I typed
>echo $user
The output is >esaad
Then in Perl, to read $user variable, I tried:
system("echo $user")
Also tried this command:
my $userName = system( echo $ENV{user} );
but Perl asked for $user initialization as Perl variable not Shell one.
How I could read this variable?

You can:
print $ENV{'user'}
the reason your system call doesn't work is that system open a new shell that doesn't source .cshrc
read this answer for more information

Either use Perl built-in system variable $ENV:
print $ENV{'user'}
Or use backslash to escape the variable $user. Perl will interpret for $user variable defined inside the Perl program without the backslash. With backslash, "echo $user" is passed as system call.
system("echo \$user")

Related

Output of bash script in perl script

I want to embed a bash script inside a perl script. I want to run my bash script by taking some variables from the perl script. Once it gets the variable the bash script takes over and tries to output the files that is created within.
How can I pass the perl script variable to my bash script ?
I do not know perl but from what I read in Google, I can embed my bash script as
system("get_files.cmd -yr ${year}");
where my bash script is get_files.cmd.
I think it might be better if you passed them as arguments to your bash script.
The perl call:
system("get_files.cmd ${var1} ${var2}");
The bash script
var1="$1"
var2="$2"
...
Where $1, $2 are the arguments passed in the command line (in order). Note that you can use the $# bash variable to get the number of arguments passed.
Hope this helps =)
The Expect module can essentially automate shell tasks such as running scripts and passing perl variables. Such that:
Use Expect;
# create an Expect object by spawning another process
my $exp = Expect->spawn($command, #params)
or die "Cannot spawn $command: $!\n";
http://search.cpan.org/~rgiersig/Expect-1.15/Expect.pod

Have perl execute shellscript & take over env vars

I have a shell script that does nothing but set a bunch of environment variables:
export MYROOTDIR=/home/myuser/mytools
export PATH=$MYROOTDIR/bin:$PATH
export MYVERSION=0.4a
I have a perl script, and I want the perl script to somehow get the perl script to operate with the env vars listed in the shell script. I need this to happen from within the perl script though, I do not want the caller of the perlscript to have to manually source the shellscript first.
When trying to run
system("sh myshell.sh")
the env vars do not "propagate up" to the process running the perl script.
Is there a way to do this?
To answer this question properly, I need to know a bit more.
Is it okay to actually run the shell script from within the perl script?
Are the variable assignments all of the form export VAR=value (i.e. with fixed assignments, no variable substitutions or command substitutions)?
Does the shell script do anything else but assign variables?
Depending on answers to these, options of different complexity exist.
Thanks for the clarification. Okay, here's how to do it. Other than assigning variables, your script has no side effects. This allows to run the script from within perl. How do we know what variables are exported in the script? We could try to parse the shell script, but that's not the Unix way of using tools that do one thing well and chain them together. Instead we use the shell's export -p command to have it announce all exported variables and their values. In order to find only the variables actually set by the script, and not all the other noise, the script is started with a clean environment using env -i, another underestimated POSIX gem.
Putting it all together:
#!/usr/bin/env perl
use strict;
use warnings;
my #cmd = (
"env", "-i", "PATH=$ENV{PATH}", "sh", "-c", ". ./myshell.sh; export -p"
);
open (my $SCRIPT, '-|', #cmd) or die;
while (<$SCRIPT>) {
next unless /^export ([^=]*)=(.*)/;
print "\$ENV{$1} = '$2'\n";
$ENV{$1} = $2;
}
close $SCRIPT;
Notes:
You need to pass to env -i all environment your myshell.sh needs, e.g. PATH.
Shells will usually export the PWD variable; if you don't want this in your perl ENV hash, add next if $1 eq 'PWD'; after the first next.
This should do the trick. Let me know if it works.
See also:
http://pubs.opengroup.org/onlinepubs/009695399/utilities/export.html
http://pubs.opengroup.org/onlinepubs/009695399/utilities/env.html
Try Shell::Source.
You can set the environment variables inside the BEGIN block.
BEGIN block is executed before the rest of the code, setting the environment variables in this block makes them visible to the rest of the code before it is compiled and run.
If you have any perl modules to 'use' based on the enviornment settings, BEGIN block makes it possible.
Perl uses a special hash %ENV to maintain the environment variables.
You can modify the contents of this hash to set the env variables.
EXAMPLE :
BEGIN
{
$ENV { 'MYROOTDIR' } = '/home/myuser/mytools';
$ENV { 'PATH' } = "$ENV{ 'MYROOTDIR' }/bin:$ENV{ 'PATH' }";
}
Wouldn't it be easier for a shell script to set the variables, and then call the perl program?
i.e.:
run.sh:
#!/bin/sh
export MYROOTDIR=/home/myuser/mytools
export PATH=$MYROOTDIR/bin:$PATH
export MYVERSION=0.4a
./program.pl
This can now be done with Env::Modify with few changes to your existing code.
use Env::Modify 'system';
...
system("sh myshell.sh");
print $ENV{MYROOTDIR}; # "/home/myuser/mytools"
or if all your shell script does is modify the environment, you can use the source function
use Env::Modify `source`;
source("myshell.sh");
print $ENV{MYVERSION}; # "0.4a"

echo does not work for PATH variable!

When I run the ls command this runs fine. But echo $PATH does not give me any output from perl. When I run echo from the shell prompt it gives me output. Can you explain this behavior?
#!usr/bin/perl
$\="\n";
$out = `ls`;
print $out;
$out=`echo $PATH`;
print $out;
Please note that while the technically correct answer to your question is the $ interpolation, you should also note that you should not treat Perl like a shell script and call external commands via backticks instead of using Perl built-in or library functions designed for the purpose:
$out = join("\n", glob("*")); # same as `ls`
$out = $ENV{PATH}; # same as `echo $PATH`
This has several significant advantages:
speed (no call to system)
portability
More security (no shell attack vector)
Most built ins cover proper error handling for you better than your own system call implementation
Nearly always a better, cleaner, shorter and easier to read/maintain code
Backticks interpolate like double quotes, so you need to escape the $.
$out=`echo \$PATH`;
$PATH is shell variable, from perl you should use it as perl variable $ENV{PATH}
Still try to read some basic docs too, like perldoc perlintro. No need for executing echo at all.
Perl is interpolating $PATH in the backticks as a Perl variable, and you've not set a $PATH anywhere in your script, so the command is coming out as
$out = `echo `
which is basically a null-op. Try
$out = `echo \$PATH`
instead, which would force Perl to ignore the $ and pass it intact to the shell.
You need to escape $ in $PATH because the backticks operator interpolates any variables.
$out=`echo \$PATH`;
You could also use qx// with single quotes. From perldoc perlop:
Using single-quote as a delimiter
protects the command from Perl's
double-quote interpolation, passing it
on to the shell instead:
$perl_info = qx(ps $$); # that's Perl's $$
$shell_info = qx'ps $$'; # that's the new shell's $$
Others have already explained the reason - variables inside backticks are interpolated, so your echo $PATH is actually becoming echo since there's no $PATH variable declared.
However, always put use strict; at the top of every Perl script you write.
Had you done so, Perl would have told you what was happening, e.g.:
Global symbol "$PATH" requires explicit package name at myscript.pl line 9
To stop variables being interpolated, either escape them (e.g. \$PATH), or, more cleanly, use e.g. qx'echo $PATH'.
Also, as others have pointed out, calling echo $PATH makes no real-world sense; if you're trying to get the contents of the PATH environment variable, just use $ENV{PATH} - however, you may have just been using it as a simple reduced demonstration case.

How to export a shell variable within a Perl script?

I have a shell script, with a list of shell variables, which is executed before entering a programming environment.
I want to use a Perl script to enter the programming environment:
system("environment_defaults.sh");
system("obe");
But when I enter the environment the variables are not set.
When you call your second command, it's not done in the environment you modified in the first command. In fact, there is no environment remaining from the first command, because the shell used to invoke "environment_defaults.sh" has already exited.
To keep the context of the first command in the second, invoke them in the same shell:
system("source environment_defaults.sh && obe");
Note that you need to invoke the shell script with source in order to perform its actions in the current shell, rather than invoking a new shell to execute them.
Alternatively, modify your environment at the beginning of every shell (e.g. with .bash_profile, if using bash), or make your environment variable changes in perl itself:
$ENV{FOO} = "hello";
system('echo $FOO');
Different sh -c processes will be called and environment variables are isolated within these.
Also doesn't calling environment_defaults.sh also make another sh process within what these variables will be set to in isolation?
Or start the Perl script with these environment variables exported and these will be set for all its child processes.
Each process gets its own environment, and each time you call "system" it runs a new process. So, what you are doing won't work. You'll have to run both commands in a single process.
Be aware, however, that after your Perl script exists, any environment variables it sets won't be available to you at the command line, because your Perl script is also a process with its own environment.
(UPDATE: Oh, this is not exactly what you asked for, but it might be useful for someone.)
If GDB is installed, you can set/modify parent shell variables with the following hack (non-strict style is used for clarity):
#!/usr/bin/perl
# export.pl
use File::Temp qw( tempfile );
%vars = (
a => 3,
b => 'pigs'
);
$ppid = getppid;
my #putvars = map { "call putenv (\"$_=$vars{$_}\")" } keys %vars;
$" = "\n";
$cmds = <<EOF;
attach $ppid
#putvars
detach
quit
EOF
($tmpfh, $tmpfn) = tempfile( UNLINK => 1 );
print $tmpfh $cmds;
`gdb -x $tmpfn`
Test:
$ echo "$a $b"
$ ./export.pl
$ echo "$a $b"
3 pigs
This can now be done with the Env::Modify module
use Env::Modify 'source'; # or use Env::Modify qw(source system);
source("environment_defaults.sh");
... environment from environment_defaults.sh is now available
... to Perl and to the following 'system' call
system("obe");

How can I pass arguments from one Perl script to another?

I have a script which I run and after it's run it has some information that I need to pass to the next script to run.
The Unix/DOS commands are like so:
perl -x -s param_send.pl
perl -x -s param_receive.pl
param_send.pl is:
# Send param
my $send_var = "This is a variable in param_send.pl...\n";
$ARGV[0] = $send_var;
print "Argument: $ARGV[0]\n";
param_receive.pl is:
# Receive param
my $receive_var = $ARGV[0];
print "Parameter received: $receive_var";
But nothing is printed. I know I am doing it wrong but from the tutorials I can't figure out how to pass a parameter from one script to the next!
You can use a pipe character on the command line to connect stdout from the first program to stdin on the second program, which you can then write to (using print) or read from (using the <> operator).
perl param_send.pl | perl param_receive.pl
If you want the output of the first command to be the arguments to the second command, you can use xargs:
perl param_send.pl | xargs perl param_receive.pl
The %ENV hash in Perl holds the environment variables such as PATH, USER, etc. Any modifications to these variables is reflected 'only' in the current process and any child process that it may spawn. The parent process (which happens to be the shell in this particular instance) does not reflect these changes so when the 'param_send.pl' script ends all changes are lost.
For e.g. if you were to do something like,
#!/usr/bin/perl
# param_send.pl
$ENV{'VAL'} = "Value to send to param_recv";
#!/usr/bin/perl
# param_recv.pl
print $ENV{'VAL'};
This wouldn't work since VAL is lost when param_send exits. One workaround is to call param_recv.pl from param_send.pl and pass the value as an environment variable or an argument,
#!/usr/bin/perl
# param_send.pl
$ENV{'VAL'} = "Value to send to param_recv";
system( $^X, "param_recv.pl");
OR
#!/usr/bin/perl
# param_send.pl
system( $^X, qw(param_recv.pl 'VAL') );
Other options include piping the output or you could check out this Perlmonks node for a more esoteric solution.
#ARGV is created at runtime and does not persist. So your second script will not be able to see the $ARGV[0] you assigned in the first script. As crashmstr points out you either need to execute the second script from the first using one of the many methods for doing so. For example:
my $send_var = "This is a variable in param_send.pl...\n";
`perl param_receive.pl $send_var`;
or use an environment variable using %ENV:
my $send_var = "This is a variable in param_send.pl...\n";
$ENV['send_var'] = $send_var;
For a more advanced solutions think about using sockets or IPC.