Initiating dynamic variables (variable variables) in bash shell script - linux

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

Related

Using "read" to set variables

In bash from the CLI I can do:
$ ERR_TYPE=$"OVERLOAD"
$ echo $ERR_TYPE
OVERLOAD
$ read ${ERR_TYPE}_ERROR
1234
$ echo $OVERLOAD_ERROR
1234
This works great to set my variable name dynamically; in a script it doesn't work. I tried:
#!/bin/env bash
ERR_TYPE=("${ERR_TYPE[#]}" "OVERLOAD" "PANIC" "FATAL")
for i in "${ERR_TYPE[#]}"
do
sh -c $(echo ${i}_ERROR=$"1234")
done
echo $OVERLOAD_ERROR # output is blank
# I also tried these:
# ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # command not found
# read ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # it never terminates
How would I set a variable as I do from CLI, but in a script? thanks
When you use dynamic variables names instead of associative arrays, you really need to question your approach.
err_type=("OVERLOAD" "PANIC" "FATAL")
declare -A error
for type in "${err_type[#]}"; do
error[$type]=1234
done
Nevertheless, in bash you'd use declare:
declare "${i}_error=1234"
Your approach fails because you spawn a new shell, passing the command OVERLOAD_ERROR=1234, and then the shell exits. Your current shell is not affected at all.
Get out of the habit of using ALLCAPSVARNAMES. One day you'll write PATH=... and then wonder why your script is broken.
If the variable will hold a number, you can use let.
#!/bin/bash
ERR_TYPE=("OVERLOAD" "PANIC" "FATAL")
j=0
for i in "${ERR_TYPE[#]}"
do
let ${i}_ERROR=1000+j++
done
echo $OVERLOAD_ERROR
echo $PANIC_ERROR
echo $FATAL_ERROR
This outputs:
1000
1001
1002
I'd use eval.
I think this would be considered bad practice though (it had some thing to do with the fact that eval is "evil" because it allows bad input or something):
eval "${i}_ERROR=1234"

Perl set and get env in different bash script

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.

Unix: What does cat by itself do?

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

Manipulating an array (printed by php-cli) in shell script

I am a newbie with shell scripts and I learnt a lot today.
This is an extension to this question Assigning values printed by PHP CLI to shell variables
I got the solution to read a variable in my shell script. Now how to manipulate an array? If I prepare an array in my PHP code and print it, and echo in my shell, it displays Array. How to access that array in the shell script? I tried the solution given in how to manipulate array in shell script
With the following code:-
PHP code
$neededConstants = array("BASE_PATH","db_host","db_name","db_user","db_pass");
$associativeArray = array();
foreach($neededConstants as $each)
{
$associativeArray[$each] = constant($each);
}
print $associativeArray;
Shell code
function getConfigVals()
{
php $PWD'/developer.php'
}
cd ..
PROJECT_ROOT=$PWD
cd developer
# func1 parameters: a b
result=$(getConfigVals)
for((cnt=0;cnt<${#result};cnt++))
do
echo ${result[$cnt]}" - "$cnt
done
I get this output:-
Array - 0
- 1
- 2
- 3
- 4
Whereas I want to get this:-
Array
BASE_PATH - /path/to/project
db_host - localhost
db_name - database
db_user - root
db_pass - root
You should debug your PHP script first to produce the valid array content, code
print $associativeArray;
will just get you the following output:
$ php test.php
Array
You can simply print the associative array in a foreach loop:
foreach ( $associativeArray as $key=>$val ){
echo "$key:$val\n";
}
giving a list of variable names + content separated by ':'
$ php test.php
BASE_PATH:1
db_host:2
db_name:3
db_user:4
db_pass:5
As for the shell script, I suggest using simple and understandable shell constructs and then get to the advanced ones (like ${#result}) to use them correctly.
I have tried the following bash script to get the variables from PHP script output to shell script:
# set the field separator for read comand
IFS=":"
# parse php script output by read command
php $PWD'/test.php' | while read -r key val; do
echo "$key = $val"
done
With bash4, you can use mapfile to populate an array and process substitution to feed it:
mapfile -t array < <( your_command )
Then you can go through the array with:
for line in "${array[#]}"
Or use indices:
for i in "${#array[#]}"
do
: use "${array[i]}"
done
You don't say what shell you're using, but assuming it's one that supports arrays:
result=($(getConfigVals)) # you need to create an array before you can ...
for((cnt=0;cnt<${#result};cnt++))
do
echo ${result[$cnt]}" - "$cnt # ... access it using a subscript
done
This is going to be an indexed array, rather than an associative array. While associative arrays are supported in Bash 4, you'll need to use a loop similar to the one in Martin Kosek's answer for assignment if you want to use them.

Accessing variable from ARGV

I'm writing a cPanel postwwwact script, if you're not familiar with the script its run after a new account is created. it relies on the user account variable being passed to the script which i then use for various things (creating databases etc). However, I can't seem to find the right way to access the variable i want. I'm not that good with shell scripts so i'd appreciate some advice. I had read somewhere that the value i wanted would be included in $ARGV{'user'} but this simply gives "root" as opposed to the value i need. I've tried looping through all the arguments (list of arguments here) like this:
#!/bin/sh
for var
do
touch /root/testvars/$var
done
and the value i want is in there, i'm just not sure how to accurately target it. There's info here on doing this with PHP or Perl but i have to do this as a shell script.
EDIT Ideally i would like to be able to call the variable by something other than $1 or $2 etc as this would create issues if an argument is added or removed
..for example in the PHP code here:
function argv2array ($argv) {
$opts = array();
$argv0 = array_shift($argv);
while(count($argv)) {
$key = array_shift($argv);
$value = array_shift($argv);
$opts[$key] = $value;
}
return $opts;
}
// allows you to do the following:
$opts = argv2array($argv);
echo $opts[‘user’];
Any ideas?
The parameters are passed to your script as a hash:
/scripts/$hookname user $user password $password
You can use associative arrays in Bash 4, or in earlier versions of Bash you can use built up variable names.
#!/bin/bash
# Bash >= 4
declare -A argv
for ((i=1;i<=${##};i+=2))
do
argv[${#:i:1}]="${#:$((i+1)):1}"
done
echo ${argv['user']}
Or
#!/bin/bash
# Bash < 4
for ((i=1;i<=${##};i+=2))
do
declare ARGV${#:i:1}="${#:$((i+1)):1}"
done
echo ${!ARGV*} # outputs all variable names that begin with ARGV
echo $ARGVuser
Running either:
$ ./argvtest user dennis password secret
dennis
Note: you can also use shift to step through the arguments, but it's destructive and the methods above leave $# ($1, $2, etc.) in place.
#!/bin/bash
# Bash < 4
# using shift (can use in Bash 4, also)
for ((i=1;i<=${##}+2;i++))
do
declare ARGV$1="$2"
# Bash 4: argv[$1}]="$2"
shift 2
done
echo ${!ARGV*}
echo $ARGVuser
If it's passed as a command-line parameter to the script, it's available as $1 if it's first parameter, $2 for the second, and so on.
Why not start off your script with something like
ARG_USER=$1
ARG_FOO=$2
ARG_BAR=$3
And then later in your script refer to $ARG_USER, $ARG_FOO and $ARG_BAR instead of $1, $2, and $3. That way, if you decide to change the order of arguments, or insert a new argument somewhere other than at the end, there is only one place in your code that you need to update the association between argument order and argument meaning.
You could even do more complex processing of $* to set your $ARG_WHATEVER variables, if it's not always going to be that all of the are specified in the same order every time.
You can do the following:
#!/bin/bash
for var in $argv; do
<do whatver you want with $var>
done
And then, invoke the script as:
$ /path/to/script param1 arg2 item3 item4 etc

Resources