Unix, bad substitution error? - linux

I have the following variables :
TYP=a1
STAT_a1=statistical
FINAL_VARIABLE=${STAT_${TYP}}
But I get an error:
-bash: ${STAT_${TYP}} : bad substitution
What I want is, that the value of FINAL_VARIABLE be 'statistical'
Please help..

You can't nest variable expansions like that. But you can use indirect variable expansion with ${!varname}:
TYP=a1
STAT_a1=statistical
STAT_var=STAT_${TYP} # This sets STAT_var to "STAT_a1"
FINAL_VARIABLE=${!STAT_var} # This sets FINAL_VARIABLE to "statistical"
BTW, I recommend avoiding all-caps variable names like TYP and FINAL_VARIABLE -- there are a number of all-caps names with special meanings to the shell and/or other programs, and if you accidentally use one of those weird things can happen.

Use eval, wrapping all the stuff you want to defer evaluating in single quotes:
eval 'FINAL_VARIABLE=${STAT_'${TYP}'}'
$ TYPE=al
$ STAT_a1=statistical
$ eval 'FINAL_VARIABLE=${STAT_'${TYP}'}'
$ echo $FINAL_VARIABLE
statistical

You can also do like this
root#myagent: tmp$ F=$(echo "${STAT_a1}"_"${TYP}")
root#myagent: tmp$ echo $F
statistical_a1

Related

Indirect expansion returns variable name instead of value

I am trying to set up some variables using indirect expansion. According to the documentation I've read, the set up should be simple:
var1=qa
qa_num=12345
varname="${var1}_ci"
echo ${!varname}
I should be getting "12345". Instead, the output is "varname". If I remove the exclamation point, I end up with "qa_ci", not "12345"
This should be a relatively simple solution, so I'm not sure what I'm missing, if anything.
Your code defines qa_num, but the varname assignment references qa_ci. As a result, your echo was expanding nonexistent qa_ci, giving empty results. Changing the varname assignment fixes the problem on my system.
Example: foo.sh:
#!/bin/bash
var1=qa
qa_num=12345
varname="${var1}_num" # <=== not _ci
echo "${!varname}" # I also added "" here as a general good practice
Output:
$ bash foo.sh
12345

Bash Nested Substring Removal (Extraction)?

I have something similar in a script I'm writing:
CMD="/path/to/cmd,there.sh"
TMP="${CMD##*/}"
echo "${TMP%%,*}"
Is there a way to nest the substring removals in line 2 & 3, or produce the same result in one-line, in pure bash, without going out to another program? The length of ${CMD} is not static. To be clear, I want the output to be simply "cmd".
I've tried the below, with various forms of brackets and quotations, but get a syntax error. This is something (I think) was allowed but isn't in new versions of Bash.
echo "${${CMD##*/}%%,*}"
Unfortunately, no, it's not possible to combine or nest string operations in bash.
With bash:
[[ $CMD =~ .*/([^,]*) ]] && echo ${BASH_REMATCH[1]}
Shell parameter substitution is primitive in that they don't provide functionalities like nesting. However, nobody prevents you from doing a sed thing here.
cmd="/path/to/cmd,there.sh" # Use lower-case identifiers for user variables
cmd=$(sed -E 's#^.*/([^,]+),.*$#\1#' <<<"$cmd")
The <<< enables the use of herestrings in bash.
I've found that zsh actually supports nested string operations, so I actually switched the interpreter to zsh for my script and the below works fine:
echo "${${CMD##*/}%%,*}"
If you want to write the script "in one line", just use ; or && to indicate the end of a line instead of a line-break:
CMD="/path/to/cmd,there.sh"; TMP="${CMD##*/}"; echo "${TMP%%,*}"
or
CMD="/path/to/cmd,there.sh" && TMP="${CMD##*/}" && echo "${TMP%%,*}"
A more elaborate answer about combining commands can be found below this question.
Disclaimer:
I understand that it is debatable whether or not this is a one-liner. But if you are visiting this question looking for a way to throw this in bash, it may answer your question regardless.

bash variable in string substitution

I am trying to do string substitution in bash, want to understand it better.
I crafted a success case like this:
a=abc_de_f
var=$a
echo ${var//_/-}
outout is abc-de-f. This works.
However, the following script fails:
a=abc_de_f
echo ${$a//_/-}
The error message is ${$a//_/-}: bad substitution.
It seems like related to how we can use a variable in substitution. Why this fails? How bash handles variables in this case?
Also, what is the best practice to handle escape characters in bash string substitution?
In the second case, you don't need the second $ as a is the string.
a=abc_de_f
echo ${a//_/-}
If you wanted to add a level of indirection, you can use ! before the variable as in
a=abc_de_f
b=a
echo ${b//_/-}
will output a, while
echo ${!b//_/-}
will output abc-de-f.
See here for a discussion on the art of escaping in BASH

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"

Bash Shell - The : Command

The colon command is a null command.
The : construct is also useful in the conditional setting of variables. For example,
: ${var:=value}
Without the :, the shell would try to evaluate $var as a command. <=???
I don't quite understand the last sentence in above statement. Can anyone give me some details?
Thank you
Try
var=badcommand
$var
you will get
bash: badcommand: command not found
Try
var=
${var:=badcommand}
and you will get the same.
The shell (e.g. bash) always tries to run the first word on each command line as a command, even after doing variable expansion.
The only exception to this is
var=value
which the shell treats specially.
The trick in the example you provide is that ${var:=value} works anywhere on a command line, e.g.
# set newvar to somevalue if it isn't already set
echo ${newvar:=somevalue}
# show that newvar has been set by the above command
echo $newvar
But we don't really even want to echo the value, so we want something better than
echo ${newvar:=somevalue}.
The : command lets us do the assignment without any other action.
I suppose what the man page writers meant was
: ${var:=value}
Can be used as a short cut instead of say
if [ -z "$var" ]; then
var=value
fi
${var} on its own executes the command stored in $var. Adding substitution parameters does not change this, so you use : to neutralize this.
Try this:
$ help :
:: :
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.

Resources