Bash two commands in curly braces? [duplicate] - bash

I've got a variable let's call it: ENV that can be set or not, and if set it's in lowercase. According to my ENV I would like to get some other variables (ex: URL_DEV or URL_PROD).
I know I can get my env in upper case with: ENV=${ENV^^} and set default value with ENV=${ENV:-DEFAULT} but is it possible to do it in one line ?
And generally, how can I combine bash operators on variables ?
I tried something like: ENV=${ENV^^:-DEFAULT} but does not work as expected.
My solution is:
ENV=${ENV:-dev}
ENV=${ENV^^}

You cannot achieve nested parameter expansion in bash shell, though its possible in zsh, so ENV=${ENV^^:-DEFAULT} operation cannot be executed by default.
You could use a ternary operator in the form of case construct in bash shell as there is no built-in operator for it (? :)
case "$ENV" in
"") ENV="default" ;;
*) ENV=${ENV^^} ;;
esac
But you shouldn't use upper case variable names for user defined shell variables. They are only meant for variables maintained by the system.

Nested parameter expansion is not possible in bash, alternatively you can check if the variable is set using [ ... ] operator:
[ -z "$ENV" ] && echo "DEFAULT" || echo ${ENV^^}

Related

Is this if condition equivalent to testing the existence of a variable? [duplicate]

This question already has answers here:
What does the '-' (dash) after variable names do here?
(3 answers)
Closed 2 years ago.
I'm digging around in some the system files on my Mac. In /etc/profile I've found the following excerpt:
if [ "${BASH-no}" != "no" ]; then
[ -r /etc/bashrc ] && . /etc/bashrc
fi
I've tried echo "${x-no}" for various choices of x and it seems like it's printing the value of x whenever x exists (i.e. has been set), and no otherwise.
Which leads me to wonder: Is this condition simply testing whether the variable x has been set?
Further questions: What exactly does - do? Is there a better way to test whether a variable has been set?
The meaning of ${BASH-no} is documented in §2.6.2, Parameter Expansion, of the Single Unix Specification:
${parameter:-[word]}
Use Default Values. If parameter is unset or null, the expansion of word (or an empty string if word is omitted) shall be substituted; otherwise, the value of parameter shall be substituted.
The meaning when the colon is omitted (as in your example) is described slightly later:
In the parameter expansions shown previously, use of the in the format shall result in a test for a parameter that is unset or null; omission of the shall result in a test for a parameter that is only unset.
Then there is a table, which may be easier to understand. Here are the relevant rows:
parameterSet and Not Null
parameterSet but Null
parameterUnset
${parameter:-word}
substitute parameter
substitute word
substitute word
${parameter-word}
substitute parameter
substitute null
substitute word
Here is a reliable, portable way to check whether a variable is not set at all. Note that I am using a + modifier instead of a - modifier in the parameter expansion:
if [ "${BASH+set}" = "" ]; then
echo 'BASH not set at all'
else
echo 'BASH is set, perhaps to the empty string'
fi
The expansion of "${BASH+set}" can only be "" if BASH is entirely unset. If BASH is set, even to the empty string, then "${BASH+set}" expands to "set" instead.
Is this condition simply testing whether the variable x has been set?
Yes, though it gets confused in the unlikely event that BASH=no.
What exactly does - do?
Here's man bash:
[...] Omitting the colon results in a test only for a parameter that is un‐set.
${parameter:-word}
Use Default Values. If parameter is unset or null (see above),
the expansion of word is substituted. Otherwise, the
value of parameter is substituted.
Is there a better way to test whether a variable has been set?
Yes: [ -v BASH ]. However, this is bash specific, so it defeats the purpose of checking if the current shell is bash before doing bash specific operations.

"unbound variable" reading a boolean parameter from shell script command line

My apology for not being able to find such a seemingly trivial thing myself.
I need to pass more than one boolean parameter to shell script (Bash) as follows:
./script --parameter1 --parameter2
and so on.
All are to be considered true if set.
In the beginning of the script, I use set -u.
Normal parameter with value passing I currently do as follows:
# this script accepts the following arguments:
# 1. mode
# 2. window
while [[ $# > 1 ]]
do
cmdline_argument="$1"
case $cmdline_argument in
-m|--mode)
mode="$2"
shift
;;
-w|--window)
window="$2"
shift
;;
esac
shift
done
I would like to add something like
-r|--repeat)
repeat=true
shift
;;
I do not understand why it does not work as expected.
It exits immediately with error:
./empire: line 450: repeat: unbound variable
Where the line 450 is:
if [ "$repeat" == true ];
When you use set -u, it's an error to dereference any variable that hasn't had a value explicitly assigned.
Thus, you need to set repeat=0 (or repeat=false) at the top of your script, or to use a dereference method that has an explicit default behavior when the value is unset; see BashFAQ #112.

Bash assignment value to variable as command substitution and print value output

I would like to achieve this in Bash: echo $(a=1)and print the value of variable a
I test eval, $$a,{}, $() but none of them work as most of the times either I got literally a=1 or in one case (I don't remember which) it tried to execute the value.
I known that I can do: a=1;echo $a but because I'm little fun one command per line (even if sometimes is getting little complicated) I was wondering if is possible to do this either with echo or with printf
If you know that $a is previously unset, you can do this using the following syntax:
echo ${a:=1}
This, and other types of parameter expansion, are defined in the POSIX shell command language specification.
If you want to assign a numeric value, another option, which doesn't depend on the value previously being unset, would be to use an arithmetic expansion:
echo $(( a = 1 ))
This assigns the value and echoes the number that has been assigned.
It's worth mentioning that what you're trying to do cannot be done in a subshell by design because a child process cannot modify the environment of its parent.

eval not working in shellscript

I am trying to get the value of a variable to be selected by name at runtime, using eval, but I don't get its value if - (hyphen) is in the name.
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=\${${DBUSERNAME}}"
eval "PASSWORD=\${${DBPASSWORD}}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
RESULT
echo USERNAME=east-1
echo PASSWORD=east-1
EXPECTED RESULT
echo USERNAME=DB_USER_dev_us-east-1
echo PASSWORD=DB_USER_dev_us-east-1
It's working fine if there is no hyphen present in the name.
Investigation
We can see what's happening by running this in shell with -x option to trace execution:
$ sh -x ./36332134.sh
+ ENV=dev
+ REGION=us-east-1
+ DBUSERNAME=DB_USER_dev_us-east-1
+ DBPASSWORD=DB_PASS_dev_us-east-1
+ eval USERNAME=${DB_USER_dev_us-east-1}
+ USERNAME=east-1
+ eval PASSWORD=${DB_PASS_dev_us-east-1}
+ PASSWORD=east-1
+ echo USERNAME=east-1
USERNAME=east-1
+ echo PASSWORD=east-1
PASSWORD=east-1
Notice that eval USERNAME=${DB_USER_dev_us-east-1} gives us USERNAME=east-1. That's parameter expansion in effect, as described in the Bash manual:
When not performing substring expansion, using the forms documented below (e.g., :-), bash tests for a parameter that is unset
or null. Omitting the colon results in a test only for a parameter
that is unset.
${parameter:-word}
Use Default Values. If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter
is substituted.
Since $DB_USER_dev_us is unset, then the expansion of ${DB_USER_dev_us-east-1} is east-1.
Workarounds
Shell doesn't allow - in variable names (including environment variables). I guess DB_USER_dev_us-east-1 was set by some non-shell program? In which case, you'll need a similar non-shell program to retrieve it, I think. I tested quoting the -, but to no avail.
If you can use Bash as your shell, you might want to use an associative array instead of composing variable names.
If you are able to change the environment variables, you might consider changing the - to (say) _, then using (Bash) ${REGION//-/_} or (otherwise) tr to transform the name:
REGION="${REGION//-/_}" # Bash
REGION="$(echo "$REGION"|tr - _)" # POSIX
You are evaluating/expanding a few too many times.
Toby's answer is exactly correct (and shows the proper debugging technique for this sort of issue) but the solution to the problem is to unwrap one level of expansion.
You wrote
eval "USERNAME=\${${DBUSERNAME}}"
which becomes
eval "USERNAME=\${DB_USER_dev_us-east-1}"
which then gets run through eval as
USERNAME=${DB_USER_dev_us-east-1}
which becomes
USERNAME=east-1
but you wanted to stop after the first expansion. That is
eval "USERNAME=\${DB_USER_dev_us-east-1}"
has already performed the expansion you wanted and gotten you the result you needed. So you don't want the \${...} bit or eval. Just
USERNAME=DB_USER_dev_us-east-1
which you get from
USERNAME=${DBUSERNAME}
Unless I've missed something or your example isn't accurate.
As they have explained the problem, here is what you can do
$> more a.sh
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=${DBUSERNAME}"
eval "PASSWORD=${DBPASSWORD}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
Results
$> ./a.sh
USERNAME=DB_USER_dev_us-east-1
PASSWORD=DB_PASS_dev_us-east-1

How to use eval to force variable update

I was reading the bash advanced scripting guide (if memory serves me right), and it said something to the extent that eval can be used to force variable updates.
So I tried this:
randomPath="/path/$var/here/" # var is not defined at this point
echo $randomPath
/path//here/
var="is" # initially defining var
eval $randomPath
zsh: no such file or directory: /path//here/
I don't understand the error message, and I'm wondering if I'm using eval properly.
The output I was expecting is:
eval $randomPath
echo $randomPath
/path/is/here
The problem is that $var is already being substituted in randomPath="/path/$var/here/", and because it is blank, randomPath is set to /path//here. You want to use single quotes to prevent the early substitution:
randomPath='/path/$var/here/'
The second problem is that eval x runs x as a command. What you want to do is return the newly evaluated variable as a string:
eval echo $randomPath
You can store it in a variable in the usual way:
randomPath=`eval echo $randomPath`

Resources