I have created a perl script which invokes two bash script. First script will set a envirnomental variable and the second will echo the environmental variable. I have given the contents of the files bellow
# perlscript.pl
print `. setnameenv.sh`;
print `. getnameenv.sh`;
# setnameenv.sh
export my_msg='hello world!'
# getnameenv.sh
echo $my_msg
now when I run the perl script perl perlscript.pl I am expecting the 'hello world' to be printed on the screen but actually I don't see any output. I there any way to do this without modifying the bash scripts?
You can embed perl into bash script,
#!/bin/bash
. setnameenv.sh
exec perl -x "$0" "$#"
#!perl
# your script below
print `. getnameenv.sh`;
From perldoc
-x
-xdirectory
tells Perl that the program is embedded in a larger chunk of unrelated text, such as in a mail message. Leading garbage will be discarded until the first line that starts with #! and contains the string "perl". Any meaningful switches on that line will be applied.
You spawn a shell, execute some commands to change its environment, then exit the shell. You never used the environment variable you created before exiting the shell. If you want a perl to see it, you're going to have to launch Perl from that shell.
. setnameenv.sh ; perlscript.pl
If you can't change how perlscript.pl is launched, you have a couple of options, none of which are that friendly. One of the options is to bootstrap.
BEGIN {
if (!length($ENV{my_msg})) {
require String::ShellQuote;
my $cmd = join(' ; ',
'. setnameenv.sh',
String::ShellQuote::shell_quote($^X, $0, #ARGV),
);
exec($cmd)
or die $!;
}
}
This can now be done in Perl with the Env::Modify module.
use Env::Modify qw(source);
source("setnameenv.sh");
# env settings from setnameenv.sh are now available to Perl
# and to the following system call
print `. getenvname.sh`; # or source again, like source("getenvname.sh")
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
Variables are only exported for the child processes.
You cannot export variables back to the father process.
You'll need another way to transport variables back to the father or the brothers.
For example, here is a example where all exported variables are saved and read from a file :
#!/bin/dash
# setnameenv.sh
export my_msg='hello world!'
export > savedVariables.sh
and
#!/bin/dash
# getnameenv.sh
. ./savedVariables.sh
echo "$my_msg"
Note : this works with dash. bash generates one line he cannot read back.
Related
I am trying to understand this below mentioned code snippet, currently i am stuck at line number 3 and after digging alot i got to know that $MYPERL is where perl binaries are defined/located and for $PERLDB is what perl debugger i,e -d:ptkdb and basically this is a perl script and some how person who coded this wrapps it to use the latest perl version. can some one tell me how i can change MYPERL variable value /home/Desktop/goudar/perl/ and execute rest of the script ?
#!/bin/sh
# -*- cperl -*-
exec $MYPERL -x $PERLDB -wS $0 ${1+"$#"}
#!perl
#line 6
### perl
use Cwd;
use Data::Dumper;
use List::MoreUtils qw/ uniq /;
use JSON;
use Mojo::JSON;
#rest of the code go here#
can someone tell what this code snippet does
It executes the embedded Perl script using the Perl interpreter specified by env var MYPERL. Options specified in env var PERLDB (if any) are passed to the interpreter. Warnings are enabled globally.
how i can change MYPERL variable value /home/Desktop/goudar/perl/ and execute rest of the script
If the process that will launch the script is a bourne-based, then
export MYPERL=/home/Desktop/goudar/perl/
That said, I don't know why you want to assign that value to the MYPERL env variable since the script expects it to be the path to a Perl interpreter.
I saw the line data=$(cat) in a bash script (just declaring an empty variable) and am mystified as to what that could possibly do.
I read the man pages, but it doesn't have an example or explanation of this. Does this capture stdin or something? Any documentation on this?
EDIT: Specifically how the heck does doing data=$(cat) allow for it to run this hook script?
#!/bin/bash
# Runs all executable pre-commit-* hooks and exits after,
# if any of them was not successful.
#
# Based on
# http://osdir.com/ml/git/2009-01/msg00308.html
data=$(cat)
exitcodes=()
hookname=`basename $0`
# Run each hook, passing through STDIN and storing the exit code.
# We don't want to bail at the first failure, as the user might
# then bypass the hooks without knowing about additional issues.
for hook in $GIT_DIR/hooks/$hookname-*; do
test -x "$hook" || continue
echo "$data" | "$hook"
exitcodes+=($?)
done
https://github.com/henrik/dotfiles/blob/master/git_template/hooks/pre-commit
cat will catenate its input to its output.
In the context of the variable capture you posted, the effect is to assign the statement's (or containing script's) standard input to the variable.
The command substitution $(command) will return the command's output; the assignment will assign the substituted string to the variable; and in the absence of a file name argument, cat will read and print standard input.
The Git hook script you found this in captures the commit data from standard input so that it can be repeatedly piped to each hook script separately. You only get one copy of standard input, so if you need it multiple times, you need to capture it somehow. (I would use a temporary file, and quote all file name variables properly; but keeping the data in a variable is certainly okay, especially if you only expect fairly small amounts of input.)
Doing:
t#t:~# temp=$(cat)
hello how
are you?
t#t:~# echo $temp
hello how are you?
(A single Controld on the line by itself following "are you?" terminates the input.)
As manual says
cat - concatenate files and print on the standard output
Also
cat Copy standard input to standard output.
here, cat will concatenate your STDIN into a single string and assign it to variable temp.
Say your bash script script.sh is:
#!/bin/bash
data=$(cat)
Then, the following commands will store the string STR in the variable data:
echo STR | bash script.sh
bash script.sh < <(echo STR)
bash script.sh <<< STR
I am writing a shell script, to read a file which has key=value pair and set those variables as environment variables. But I have a doubt, if I do source file.txt will that set the variables defined in that file as environment variable or I should read the file line by line and set it using export command ?
Is source command in this case different than export?
When you source the file, the assignments will be set but the variables are not exported unless the allexport option has been set. If you want all the variables to be exported, it is much simpler to use allexport and source the file than it is to read the file and use export explicitly. In other words, you should do:
set -a
. file.txt
(I prefer . because it is more portable than source, but source works just fine in bash.)
Note that exporting a variable does not make it an environment variable. It just makes it an environment variable in any subshell.
source (.) vs export (and also some file lock [flock] stuff at the end):
In short:
source some_script.sh, or the POSIX-compliant equivalent, . some_script.sh, brings variables in from other scripts, while
export my_var="something" pushes variables out to other scripts/processes which are called/started from the current script/process.
Using source some_script.sh or . some_script.sh in a Linux shell script is kind of like using import some_module in Python, or #include <some_header_file.h> in C or C++. It brings variables in from the script being sourced.
Using export some_var="something" is kind of like setting that variable locally, so it is available for the rest of the current script or process, and then also passing it in to any and all sub-scripts or processes you may call from this point onward.
More details:
So, this:
# export `some_var` so that it is set and available in the current script/process,
# as well as in all sub-scripts or processes which are called from the
# current script/process
export some_var="something"
# call other scripts/processes, passing in `some_var` to them automatically
# since it was just exported above!
script1.sh # this script now gets direct access to `some_var`
script2.sh # as does this one
script3.sh # and this one
is as though you had done this:
# set this variable for the current script/process only
some_var="something"
# call other scripts/processes, passing in `some_var` to them **manually**
# so they can use it too
some_var="something" script1.sh # manually pass in `some_var` to this script
some_var="something" script2.sh # manually pass in `some_var` to this script
some_var="something" script3.sh # manually pass in `some_var` to this script
except that the first version above, where we called export some_var="something" actually has a recursive passing or exporting of variables to sub-processes, so if we call script1.sh from inside our current script/process, then script1.sh will get the exported variables from our current script, and if script1.sh calls script5.sh, and script5.sh calls script10.sh, then both of those scripts as well will get the exported variables automatically. This is in contrast to the manual case above where only those scripts called explicitly with manually-set variables as the scripts are called will get them, so sub-scripts will NOT automatically get any variables from their calling scripts!
How to "un-export" a variable:
Note that once you've exported a variable, calling unset on it will "unexport it", like this:
# set and export `some_var` so that sub-processes will receive it
export some_var="something"
script1.sh # this script automatically receives `some_var`
# unset and un-export `some_var` so that sub-processes will no longer receive it
unset some_var
script1.sh # this script does NOT automatically receive `some_var`
In summary:
source or . imports.
export exports.
unset unexports.
Example:
Create this script:
source_and_export.sh:
#!/bin/bash
echo "var1 = $var1"
var2="world"
Then mark it executable:
chmod +x source_and_export.sh
Now here is me running some commands at the terminal to test the source (.) and export commands with this script. Type in the command you see after the lines beginning with $ (not including the comments). The other lines are the output. Run the commands sequentially, one command at a time:
$ echo "$var1" # var1 contains nothing locally
$ var1="hello" # set var1 to something in the current process only
$ ./source_and_export.sh # call a sub-process
var1 = # the sub-process can't see what I just set var1 to
$ export var1 # **export** var1 so sub-processes will receive it
$ ./source_and_export.sh # call a sub-process
var1 = hello # now the sub-process sees what I previously set var1 to
$ echo "$var1 $var2" # but I can't see var2 from the subprocess/subscript
hello
$ . ./source_and_export.sh # **source** the sub-script to _import_ its var2 into the current process
var1 = hello
$ echo "$var1 $var2" # now I CAN see what the subprocess set var2 to because I **sourced it!**
hello world # BOTH var1 from the current process and var2 from the sub-process print in the current process!
$ unset var1 # unexport (`unset`) var1
$ echo "$var1" # var1 is now NOT set in the current process
$ ./source_and_export.sh # and the sub-process doesn't receive it either
var1 =
$ var1="hey" # set var1 again in the current process
$ . ./source_and_export.sh # if I **source** the script, it runs in the current process, so it CAN see var1 from the current process!
var1 = hey # notice it prints
$ ./source_and_export.sh # but if I run the script as a sub-process, it can NOT see var1 now because it was `unset` (unexported)
var1 = # above and has NOT been `export`ed again since then!
$
Using files as global variables between processes
Sometimes, when writing scripts to launch programs and things especially, I have come across cases where export doesn't seem to work right. In these cases, sometimes one must resort to using files themselves as global variables to pass information from one program to another. Here is how that can be done. In this example, the existence of the file "~/temp/.do_something" functions as an inter-process boolean variable:
# In program A, if the file "~/temp/.do_something" does NOT exist,
# then create it
mkdir -p ~/temp
if [ ! -f ~/temp/.do_something ]; then
touch ~/temp/.do_something # create the file
fi
# In program B, check to see if the file exists, and act accordingly
mkdir -p ~/temp
DO_SOMETHING="false"
if [ -f ~/temp/.do_something ]; then
DO_SOMETHING="true"
fi
if [ "$DO_SOMETHING" == "true" ] && [ "$SOME_OTHER_VAR" == "whatever" ]; then
# remove this global file "variable" so we don't act on it again
# until "program A" is called again and re-creates the file
rm ~/temp/.do_something
do_something
else
do_something_else
fi
Simply checking for the existence of a file, as shown above, works great for globally passing around boolean conditions between programs and processes. However, if you need to pass around more complicated variables, such as strings or numbers, you may need to do this by writing these values into the file. In such cases, you should use the file lock function, flock, to properly ensure inter-process synchronization. It is a type of process-safe (ie: "inter-process") mutex primitive. You can read about it here:
The shell script flock command: https://man7.org/linux/man-pages/man1/flock.1.html. See also man flock or man 1 flock.
The Linux library C command: https://man7.org/linux/man-pages/man2/flock.2.html. See also man 2 flock. You must #include <sys/file.h> in your C file to use this function.
References:
https://askubuntu.com/questions/862236/source-vs-export-vs-export-ld-library-path/862256#862256
My own experimentation and testing
I'll be adding the above example to my project on GitHub here, under the bash folder: https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world
I am writing a shell script to set the environment variables whose values are available in a file. Below is the shell script I wrote,
VARIABLE_FILE=env-var.dat
if [ -f ${VARIABLE_FILE} ] ; then
. ${VARIABLE_FILE}
if [ ! -z "${TEST_VAR1}" ] ; then
export TEST_VAR1="${TEST_VAR1}"
fi
if [ ! -z "${TEST_VAR2}" ] ; then
export TEST_VAR2="${TEST_VAR2}"
fi
fi
The above code works only in bash shell, since I have used export command to set the environment variable and it fails if I used it with any other shell. Is there is any command to set the environment variable which works in any shell ?
"Fancier" shells like bash and zsh permit you to set a variable and export it as an environment variable at the same time like so:
export FOO=bar
With a standard POSIX bourne shell, the equivalent is achieved by doing it in two commands:
FOO=bar
export FOO
Note that once you've exported a variable, you can reset it to a different value later in the script and it's still exported (you don't need to export it again). Also, you can export several variables at a time:
FOO=bar
BAZ=quux
export FOO BAZ
You mentioned tcsh in your comment, but csh and derivatives are completely different from bourne-based shells (and not recommended for use!). You can rarely make a shell script compatible with both sh and csh at the same time. For csh, look into setenv
If you really want this to happen, it can be done, but it's tricky. One way to do it is to use awk to output the correct syntax and evaluate the text coming back from awk. To share a single environment variable value file between major sh and csh flavors, the following command in a file will import a variable value file to the environment: (yes, yes, it's one huge line, due to the inflexible way that some shells treat the backticks. If you didn't mind having a .awk file too, you could use awk -f...)
eval `awk '{ var = $1; $1=""; val=substr($0,2); if ( ENVIRON["SHELL"] ~ /csh$/) { print "setenv", var, " \"" val "\";" } else { print var "=\"" val "\"" ; print "export", var }}' $HOME/env_value_file`
The variable value file is in this format:
FOO value for foo
BAR foo bar
BAZ $BAR plus values $FOO
Design notes for educational purposes:
In awk, there's no easy way of accessing fields 2-NF, so if there
could be spaces in our variable values we need to modify $1 to get
$0 to be close to get the value we want.
To get this to work, since a SHELL variable is always set, but not as an
environment variable and not with a consistent capitalization, you have to wet
a SHELL environment variable from the shell's value as below.
as an environment variable before you use the script.
Also, if you want the new environment values to be present after the import
environment script you need to source the environment script.
If a shell doesn't do eval well, you'll have to tweak the script.
For bourne shell flavors (bash, sh, ksh, zsh):
export SHELL
. import_environment
For csh flavors: (shell variable tends to be lower case in csh shells)
setenv SHELL "$shell"
source import_environment
I am using PHP CLI through bash shell. Please check Manipulating an array (printed by php-cli) in shell script for details.
In the following shell code I am able to echo the key- value pairs that I get from the PHP script.
IFS=":"
# parse php script output by read command
php $PWD'/test.php' | while read -r key val; do
echo $key":"$val
done
Following is the output for this -
BASE_PATH:/path/to/project/root
db_host:localhost
db_name:database
db_user:root
db_pass:root
Now I just want to initiate dynamic variables inside the while loop so that I can use them like $BASE_PATH having value '/path/to/project/root', $db_host having 'localhost'
I come from a PHP background. I would like something like $$key = $val of PHP
Using eval introduces security risks that must be considered. It's safer to use declare:
# parse php script output by read command
while IFS=: read -r key val; do
echo $key":"$val
declare $key=$val
done < <(php $PWD'/test.php')
If you are using Bash 4, you can use associative arrays:
declare -A some_array
# parse php script output by read command
while IFS=: read -r key val; do
echo $key":"$val
some_array[$key]=$val
done < <(php $PWD'/test.php')
Using process substition <() and redirecting it into the done of the while loop prevents the creation of a subshell. Setting IFS for only the read command eliminates the need to save and restore its value.
You may try using the eval construct in BASH:
key="BASE_PATH"
value="/path/to/project/root"
# Assign $value to variable named "BASE_PATH"
eval ${key}="${value}"
# Now you have the variable named BASE_PATH you want
# This will get you output "/path/to/project/root"
echo $BASE_PATH
Then, just use it in your loop.
EDIT: this read loop creates a sub-shell which will not allow you to use them outside of the loop. You may restructure the read loop so that the sub-shell is not created:
# get the PHP output to a variable
php_output=`php test.php`
# parse the variable in a loop without creating a sub-shell
IFS=":"
while read -r key val; do
eval ${key}="${val}"
done <<< "$php_output"
echo $BASE_PATH