Idiomatic bash: set default value for variable [duplicate] - bash

This question already has answers here:
Assigning default values to shell variables with a single command in bash
(11 answers)
Closed 8 years ago.
How can I set a default value for a bash variable in a concise, idiomatic way? This just looks ugly:
if [[ ! -z "$1" ]]; then
option="$1"
else
option="default"
fi

default value : ${parameter:-word} \
assign default value : ${parameter:=word} |_ / if unset or null -
error if empty/unset : ${parameter:?mesg} | \ use no ":" for unset only
use word unless empty/unset : ${parameter:+word} /

You can use:
option=${1:-default}
This sets option to the first command line parameter if a first command line parameter was given and not null. Otherwise, it sets option to default. See Bash reference manual for the details on parameter expansion and some useful variants of this form.

For uses other than assigning a default value, which Toxaris already covered, it's worth mentioning that there is a reverse test for -z, so instead of
if [[ ! -z "$1" ]]; then
do_something
fi
you can simply write:
if [ -n "$1" ]; then
do_something
fi
and if there is no else branch, you can shorten it to:
[ -n "$1" ] && do_something

Related

Check ENV variables in a loop to see if they were set [duplicate]

This question already has an answer here:
Bash indirect variable referencing
(1 answer)
Closed 5 months ago.
Thought it was a simple thing, but not sure why it got messed up
I'm trying in a for-loop to check a few ENV variables and make sure they were set.
This is the current code
# Bash 4.2 ( not 4.4 )
# Verify ENV
declare -a env_vars=(
"HOME",
"PATH",
"PYTHONPATH",
"TESTNONEXISTING"
)
for evar in "${env_vars[#]}"; do
# Tried: printenv
# Tried: -v $evar
# Tried: eval
if [[ -z "${evar}" ]]; then
echo
echo "ERROR: ENV var '$evar' is missing"
echo
exit 1
fi
done
I tried many things suggested here in StackOverflow - but they don't work when used in a loop ( as a string )
An example of something that works, but it's useless ...
required_env () {
ename=$1
evalue=$2
if [[ -z "$evalue" ]]; then
echo "ENV variable '$ename' is missing"
exit 1
else
echo "Variable exists"
fi
}
required_env "HOME" $HOME
required_env "PATH" $PATH
etc ...
The problem is mainly - how to convert a string - to a real variable inside the "if" - that's what I cannot get ..
Any suggestions ?
-z tests if the string is not empty. On the first iteration evar=HOME, sp [[ -z "${evar}" ]] becomes [[ -z "HOME" ]]. You are checking if the name of the variable is not empty, not the value of the variable.
You can just check if a variable is set with -v.
[[ -v "$evar" ]]
Note that there is a difference between unset and (set and empty) and (set and not-empty). -z checks for unset or (set and empty).
how to convert a string - to a real variable inside the "if"
Use variable indirection.
[[ -z "${!evar}" ]]

bash scription if conditional "${1:-}" [duplicate]

This question already has answers here:
Usage of :- (colon dash) in bash
(2 answers)
Closed 1 year ago.
I try to understant the if condition
log_daemon_msg () {
if [ -z "${1:-}" ]; then
return 1
fi
log_daemon_msg_pre "$#"
if [ -z "${2:-}" ]; then
echo -n "$1:" || true
return
fi
echo -n "$1: $2" || true
log_daemon_msg_post "$#"
}
what is mean "${1:-}" and "${2:-}"
See Shell Parameter Expansion, {parameter:-word}.
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
So ${1:-} is the first argument of the function log_daemon_msg or the empty string if the function was called without arguments or with an empty first argument.
Normally, that doesn't really make sense, as just writing $1 would have the same effect. However, if your script runs with set -u (exit when using an undefined variable) ${1:-} can be used to get the standard-behavior ($1 turns into the empty string if unset). But the echo -n "$1: $2" at the end would still fail in the case of missing arguments.

Boolean variables in a shell script [duplicate]

This question already has answers here:
How can I declare and use Boolean variables in a shell script?
(25 answers)
Closed 5 years ago.
I follow this post
How to declare and use boolean variables in shell script?
and developed a simple shell script
#!/bin/sh
a=false
if [[ $a ]];
then
echo "a is true"
else
echo "a is false"
fi
The output is
a is true
What is wrong?
This doesn't work since [[ only tests if the variable is empty.
You need write:
if [[ $a = true ]];
instead of only
if [[ $a ]];
You need to check if the value equals true, not just whether the variable is set or not. Try the following:
if [[ $a = true ]];
Note that true and false are actually builtin commands in bash, so you can omit the conditional brackets and just do:
if $a;
Update: after reading this excellent answer, I rescind this advice.
The reason if [[ $a ]] doesn't work like you expect is that when then [[ command receives only a single argument (aside from the closing ]]), the return value is success if the argument is non-empty. Clearly the string "false" is not the empty string. See https://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs and https://www.gnu.org/software/bash/manual/bashref.html#Bash-Conditional-Expressions

Use string as bash variable name in alternative value expansion [duplicate]

This question already has answers here:
How can I look up a variable by name with #!/bin/sh (POSIX sh)?
(4 answers)
Closed 7 years ago.
How can I use the value of one variable as the name of another variable in an alternative value expansion (${var+alt}) in bash?
I would think that
#!/bin/bash
cat='dog'
varname='cat'
if [ -z ${`echo "${varname}"`+x} ]; then
echo 'is null'
fi
should be roughly equivalent to
#!/bin/bash
if [ -z ${dog+x} ]; then
echo 'is null'
fi
but when I try to do this, I get
${`echo "${cat}"`+x}: bad substitution
I guess part of the problem is that the subshell doing the command substitution doesn't know about $varname anymore? Do I need to export that variable?
My reason for doing this is that I learned from this answer how to check if a variable is null, and I'm trying to encapsulate that check in a function called is_null, like this:
function is_null {
if [ $# != 1 ]; then
echo "Error: is_null takes one argument"
exit
fi
# note: ${1+x} will be null if $1 is null, but "x" if $1 is not null
if [ -z ${`echo "${1}"`+x} ]; then
return 0
else
return 1
fi
}
if is_null 'some_flag'; then
echo 'Missing some_flag'
echo $usage
exit
fi
I'm not sure if I understand your problem.
If I got, what you need is eval command.
$ cat='dog'
$ varname='cat'
$ echo ${varname}
cat
$ eval echo \$${varname}
dog

Test for a Bash variable being unset, using a function

A simple Bash variable test goes:
${varName:? "${varName} is not defined"}
I'd like to reuse this, by putting it in a function. How can I do it?
The following fails
#
# Test a variable exists
tvar(){
val=${1:? "${1} must be defined, preferably in $basedir"}
if [ -z ${val} ]
then
echo Zero length value
else
echo ${1} exists, value ${1}
fi
}
I.e., I need to exit if the test fails.
Thanks to lhunath's answer, I was led to a part of the Bash man page that I've overlooked hundreds of times:
When not performing substring expansion, bash tests for a parameter that is unset or null; omitting the colon results in a test only for a parameter that is unset.
This prompted me to create the following truth table:
Unset
Set, but null
Set and not null
Meaning
${var-_}
T
F
T
Not null or not set
${var:-_}
T
T
T
Always true, use for subst.
$var
F
F
T
'var' is set and not null
${!var[#]}
F
T
T
'var' is set
This table introduces the specification in the last row. The Bash man page says "If name is not an array, expands to 0 if name is set and null otherwise." For purposes of this truth table, it behaves the same even if it's an array.
You're looking for indirection.
assertNotEmpty() {
: "${!1:? "$1 is empty, aborting."}"
}
That causes the script to abort with an error message if you do something like this:
$ foo=""
$ assertNotEmpty foo
bash: !1: foo is empty, aborting.
If you just want to test whether foo is empty, instead of aborting the script, use this instead of a function:
[[ $foo ]]
For example:
until read -p "What is your name? " name && [[ $name ]]; do
echo "You didn't enter your name. Please, try again." >&2
done
Also, note that there is a very important difference between an empty and an unset parameter. You should take care not to confuse these terms! An empty parameter is one that is set, but just set to an empty string. An unset parameter is one that doesn't exist at all.
The previous examples all test for empty parameters. If you want to test for unset parameters and consider all set parameters OK, whether they're empty or not, use this:
[[ ! $foo && ${foo-_} ]]
Use it in a function like this:
assertIsSet() {
[[ ! ${!1} && ${!1-_} ]] && {
echo "$1 is not set, aborting." >&2
exit 1
}
}
Which only aborts the script when the parameter name you pass denotes a parameter that isn't set:
$ ( foo="blah"; assertIsSet foo; echo "Still running." )
Still running.
$ ( foo=""; assertIsSet foo; echo "Still running." )
Still running.
$ ( unset foo; assertIsSet foo; echo "Still running." )
foo is not set, aborting.
You want to use [ -z ${parameter+word} ]
Some part of man bash:
Parameter Expansion
...
In each of the cases below, word is subject to tilde expansion, parameter expansion, command substitution, and
arithmetic expansion. When not performing substring expansion, 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 Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of
word is substituted.
...
in other words:
${parameter+word}
Use Alternate Value. If parameter is unset, nothing is substituted, otherwise the expansion of
word is substituted.
some examples:
$ set | grep FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
it is unset
$ declare FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ FOOBAR=
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ FOOBAR=1
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ unset FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
it is unset
$
This function tests for variables that are currently set. The variable may even be an array. Note that in Bash: 0 == TRUE, 1 == FALSE.
function var.defined {
eval '[[ ${!'$1'[#]} ]]'
}
# Typical usage of var.defined {}
declare you="Your Name Here" ref='you';
read -p "What's your name: " you;
if var.defined you; then # Simple demo using literal text
echo "BASH recognizes $you";
echo "BASH also knows a reference to $ref as ${!ref}, by indirection.";
fi
unset you # Have just been killed by a master :D
if ! var.defined $ref; then # Standard demo using an expanded literal value
echo "BASH doesn't know $ref any longer";
fi
read -s -N 1 -p "Press any key to continue...";
echo "";
So to be clear here, the function tests literal text. Every time a command is called in Bash, variables are generally 'swapped-out' or 'substituted' with the underlying value unless:
$varRef ($) is escaped: $varRef
$varRef is single quoted '$varRef'
I.e., I need to exit if the test fails.
The code:
${varName:? "${varName} is not defined"}
will return a nonzero exit code when there is not a variable named "varName". The exit code of the last command is saved in $?.
About your code:
val=${1:? "${1} must be defined, preferably in $basedir"}
Maybe it is not doing what you need. In the case that $1 is not defined, the "${1}" will be substituted with nothing. Probably you want use the single quotes that literally writes ${1} without substitution.
val=${1:? '${1} must be defined, preferably in $basedir'
I am unsure if this is exactly what you want, but a handy trick I use when writing a new and complex script is to use "set -o":
set -o # Will make the script bomb out when it finds an unset variable
For example,
$ grep '$1' chex.sh
case "$1" in
$ ./chex.sh
./chex.sh: line 111: $1: unbound variable
$ ./chex.sh foo
incorrect/no options passed.. exiting
if set | grep -q '^VARIABLE='
then
echo VARIABLE is set
fi

Resources