using variable to make variable name in shell script - shell

I want to echo $ANMIAL0 and then $ANIMAL1 using a script below.
But I get line 7: ${ANIMAL$i}: bad substitution error message. What's wrong?
#!/bin/sh
ANIMAL0="tiger"
ANIMAL1="lion"
i=0
while test $i -lt 2; do
echo "Hey $i !"
echo ${ANIMAL$i}
i=`expr $i + 1`
done

You're probably better off using an array instead of ANIMAL0 and ANIMAL1. Something like this maybe?
#!/bin/bash
animals=("Tiger" "Lion")
for animal in ${animals[*]}
do
printf "Hey, ${animal} \n"
done
Using eval will get you into trouble down the road and is not best practice for what you're trying to do.

The problem is that the shell normally performs a single substitution pass. You can force a second pass with eval, but this obviously comes with the usual security caveats (don't evaluate unchecked user input, etc).
eval echo \$ANIMAL$i
Bash has various constructs to help avoid eval.

You can use eval
eval echo \$ANIMAL$i

Related

Bad Substitution - Variable name inside other variable name - in Bash

I have a problem in one of my scripts, here it is simplified.
I want to name a variable using another variable in it. My script is:
#! /bin/bash
j=1
SAMPLE${j}_CHIP=5
echo ${SAMPLE${j}_CHIP}
This script echoes:
line 3: SAMPLE1_CHIP=5: command not found
line 4: ${SAMPLE${j}_CHIP}: bad substitution
I'm trying to do that in order to name several samples in a while loop changing the "j" parameter.
Anyone knows how to name a variable like that?
It's possible with eval, but don't use dynamic variable names. Arrays are much, much better.
$ j=1
$ SAMPLES[j]=5
$ echo ${SAMPLES[j]}
5
You can initialize an entire array at once like so:
$ SAMPLES=(5 10 15 20)
And you can append with:
$ SAMPLES+=(25 30)
Indices start at 0.
To read the value of the variable, you may use indirection: ${!var}:
#! /bin/bash
j=1
val=get_5
var=SAMPLE${j}_CHIP
declare "$var"="$val"
echo "${!var}"
The problem is to make the variable get the value.
I used declare above, and the known options are:
declare "$var"="$val"
printf -v "$var" '%s' "$val"
eval $var'=$val'
export "$var=$val"
The most risky option is to use eval. If the contents of var or val may be set by an external user, you have set a way to get code injection. It may seem safe today, but after someone edit the code for some reason, it may get changed to give an attacker a chance to "get in".
Probably the best solution is to avoid all the above.
Associative Array
One alternative is to use Associative Arrays:
#! /bin/bash
j=1
val=get_5
var=SAMPLE${j}_CHIP
declare -A array
array[$var]=$val
echo "${array[$var]}"
Quite less risky and you get a similar named index.
Plain array
But it is clear that the safest solution is to use the simplest of solutions:
#! /bin/bash
j=1
val=get_5
array[j]=$val
echo "${array[j]}"
All done, little risk.
If you really want to use variable variables:
#! /bin/bash
j=1
var="SAMPLE${j}_CHIP"
export ${var}=5
echo "${!var}" # prints 5
However, there are other approaches to solving the parent issue, which are likely less confusing than this approach.
j=1
eval "SAMPLE${j}_CHIP=5"
echo "${SAMPLE1_CHIP}"
Or
j=1
var="SAMPLE${j}_CHIP"
eval "$var=5"
echo "${!var}"
As others said, it's normally not possible. Here is a workaround if you wish. Note that you have to use eval when declaring a nested variable, and ⭗ instead of $ when accessing it (I use ⭗ as a function name, because why not).
#!/bin/bash
function ⭗ {
if [[ ! "$*" = *\{*\}* ]]
then echo $*
else ⭗ $(eval echo $(echo $* | sed -r 's%\{([^\{\}]*)\}%$(echo ${\1})%'))
fi
}
j=1
eval SAMPLE${j}_CHIP=5
echo `⭗ {SAMPLE{j}_CHIP}`
c=CHIP
echo `⭗ {SAMPLE{j}_{c}}`

trouble capturing output of a subshell that has been backgrounded

Attempting to make a "simple" parallel function in bash. The problem is currently that when the line to capture the output is backgrounded, the output is lost. If that line is not backgrounded, the output is captured fine, but this of course defeats the purpose of the function.
#!/usr/bin/env bash
cluster="${1:-web100s}"
hosts=($(inventory.pl bash "$cluster" | sort -V))
cmds="${2:-uptime}"
parallel=10
cx=0
total=0
for host in "${hosts[#]}"; do
output[$total]=$(echo -en "$host: ")
echo "${output[$total]}"
output[$total]+=$(ssh -o ConnectTimeout=5 "$host" "$cmds") &
cx=$((cx + 1))
total=$((total + 1))
if [[ $cx -gt $parallel ]]; then
wait >&/dev/null
cx=0
fi
done
echo -en "***** DONE *****\n Results\n"
for ((i=0; i<= $total; i++)); do
echo "${output[$i]}"
done
That's because your command (the assignment) is run in a subshell, so this assignment can't influence the parent shell. This boils down to this:
a=something
a='hello senorsmile' &
echo "$a"
Can you guess what the output is? the output is, of course,
something
and not hello senorsmile. The only way for the subshell to communicate with the parent shell is to use an IPC (interprocess communication), in one form or another. I don't have any solution to propose, I only tried to explain why it fails.
If you think of it, it should make sense. What do you think of this?
a=$( echo a; sleep 1000000000; echo b ) &
The command immediately returns (after forking)... but the output is only going to be fully available in... over 31 years.
Assigning a shell variable in the background this way is effectively meaningless. Bash does have built in co-processing which should work for you:
http://www.gnu.org/software/bash/manual/bashref.html#Coprocesses

checking if output of a curl commnd is null or not in shell script

I am writing a shell script and the curl command is stored in a variable like this:
str="curl 'http...'"
and I am evaluating this using
eval $str
I want to check whether the output of this is null or not,means it is returning me something or not.How can I check this?
Why cant I store the output in a variable like this:
op=eval $str
Why does wriring this gives error?
Thanks in advance
Use command substitution:
str="curl 'http...'"
op=$(eval "$str")
if [[ -n $op ]]; then
echo "Output is not null."
else
echo "Output is null.
fi
A better approach actually instead of using eval, is to use arrays as it's safer:
cmd=("curl" "http://...")
op=$("${cmd[#]}")
if [[ -n $op ]]; then
echo "Output is not null."
else
echo "Output is null.
fi
When assigning the array, separate every argument as a single element. You can pick any quoting method whether by the use of single quotes or double quotes if quoting is necessary to keep an argument as one complete argument.
By the way you simply had an error since it wasn't the proper way to call a command in which you'd get the output. As explained you have to use command substitution. An advanced method called process substitution could also be applicable but it's not practical for requirement.

bash, substitute part of variable by another variable

#!/bin/bash
SUNDAY_MENU=BREAD
MONDAY_MENU=APPLES
TODAY=MONDAY
ECHO "I want ${${TODAY}_MENU}" # does not work, bad substitution
ECHO "I want ${`echo $TODAY`_MENU}" # does not work, bad substitution
Any Ideas ?
Use variable indirection like this:
varname=${TODAY}_MENU
echo ${!varname}
If you are using Bash 4 or later, however, you are probably better off using an associative array:
menu=([sunday]=bread [monday]=apples)
echo ${menu[$TODAY]}
I use eval function
#!/bin/bash
SUNDAY_MENU=BREAD
MONDAY_MENU=APPLES
TODAY=MONDAY
eval TODAY_MENU=\$\{${TODAY}_MENU\}
echo "I want ${TODAY_MENU}"

How to use the read command in Bash?

When I try to use the read command in Bash like this:
echo hello | read str
echo $str
Nothing echoed, while I think str should contain the string hello. Can anybody please help me understand this behavior?
The read in your script command is fine. However, you execute it in the pipeline, which means it is in a subshell, therefore, the variables it reads to are not visible in the parent shell. You can either
move the rest of the script in the subshell, too:
echo hello | { read str
echo $str
}
or use command substitution to get the value of the variable out of the subshell
str=$(echo hello)
echo $str
or a slightly more complicated example (Grabbing the 2nd element of ls)
str=$(ls | { read a; read a; echo $a; })
echo $str
Other bash alternatives that do not involve a subshell:
read str <<END # here-doc
hello
END
read str <<< "hello" # here-string
read str < <(echo hello) # process substitution
Typical usage might look like:
i=0
echo -e "hello1\nhello2\nhello3" | while read str ; do
echo "$((++i)): $str"
done
and output
1: hello1
2: hello2
3: hello3
The value disappears since the read command is run in a separate subshell: Bash FAQ 24
To put my two cents here: on KSH, reading as is to a variable will work, because according to the IBM AIX documentation, KSH's read does affects the current shell environment:
The setting of shell variables by the read command affects the current shell execution environment.
This just resulted in me spending a good few minutes figuring out why a one-liner ending with read that I've used a zillion times before on AIX didn't work on Linux... it's because KSH does saves to the current environment and BASH doesn't!
I really only use read with "while" and a do loop:
echo "This is NOT a test." | while read -r a b c theRest; do
echo "$a" "$b" "$theRest"; done
This is a test.
For what it's worth, I have seen the recommendation to always use -r with the read command in bash.
You don't need echo to use read
read -p "Guess a Number" NUMBER
Another alternative altogether is to use the printf function.
printf -v str 'hello'
Moreover, this construct, combined with the use of single quotes where appropriate, helps to avoid the multi-escape problems of subshells and other forms of interpolative quoting.
Do you need the pipe?
echo -ne "$MENU"
read NUMBER

Resources