Check if variables exist in a for loop in bash - bash

I need to check a lot of environment variables that need to be set in order for my bash script to run. I've seen this question and tried
thisVariableIsSet='123'
variables=(
$thisVariableIsSet
$thisVariableIsNotSet
)
echo "check with if"
# this works
if [[ -z ${thisVariableIsNotSet+x} ]]; then
echo "var is unset";
else
echo "var is set to '$thisVariableIsNotSet'";
fi
echo "check with for loop"
# this does not work
for variable in "${variables[#]}"
do
if [[ -z ${variable+x} ]]; then
echo "var is unset";
else
echo "var is set to '$variable'";
fi
done
The output is:
mles:tmp mles$ ./test.sh
check with if
var is unset
check with for loop
var is set to '123'
If I'm checking the not set variable in an if block, the check works (var is unset). However in the for loop the if block only prints if a variable is set, not if a variable ist not set.
How can I check variables in a for loop?

You can try to use indirect expansion ${!var}:
thisVariableIsSet='123'
variables=(
thisVariableIsSet # no $
thisVariableIsNotSet
)
echo "check with if"
# this works
if [[ -z ${thisVariableIsNotSet+x} ]]; then
echo "var is unset";
else
echo "var is set to '$thisVariableIsNotSet'";
fi
echo "check with for loop"
# this does not work
for variable in "${variables[#]}"
do
if [[ -z ${!variable+x} ]]; then # indirect expansion here
echo "var is unset";
else
echo "var is set to ${!variable}";
fi
done
Output:
check with if
var is unset
check with for loop
var is set to 123
var is unset

Related

Bash function to check if a given variable is set

As explained in this answer, the "right" way to check if a variable is set in bash looks like this:
if [ -z ${var+x} ]; then
echo "var is unset"
else
echo "var is set to '$var'"
fi
What I'm interested in is how to extract this into a function that can be reused for different variables.
The best I've been able to do so far is:
is_set() {
local test_start='[ ! -z ${'
local test_end='+x} ]'
local tester=$test_start$1$test_end
eval $tester
}
It seems to work, but is there a better way that doesn't resort to calling eval?
In Bash you can use [[ -v var ]]. There's no need for a function or convoluted schemes.
From the manpage:
-v varname
True if the shell variable varname is set (has been assigned a value).
The first 2 command sequences prints ok:
[[ -v PATH ]] && echo ok
var="" ; [[ -v var ]] && echo ok
unset var ; [[ -v var ]] && echo ok
With [[ ... ]], you can simply do this (there is no need for double quotes):
[[ $var ]] && echo "var is set"
[[ $var ]] || echo "var is not set or it holds an empty string"
If you really want a function, then we can write one-liners:
is_empty() { ! [[ $1 ]]; } # check if not set or set to empty string
is_not_empty() { [[ $1 ]]; } # check if set to a non-empty string
var="apple"; is_not_empty "$var" && echo "var is not empty" # shows "var is not empty"
var="" ; is_empty "$var" && echo "var is empty" # shows "var is empty"
unset var ; is_empty "$var" && echo "var is empty" # shows "var is empty"
var="apple"; is_empty "$var" || echo "var is not empty" # shows "var is not empty"
Finally, is_unset and is_set could be implemented to treat $1 as the name of the variable:
is_unset() { [[ -z "${!1+x}" ]]; } # use indirection to inspect variable passed through $1 is unset
is_set() { ! [[ -z "${!1+x}" ]]; } # check if set, even to an empty string
unset var; is_unset var && echo "var is unset" # shows "var is unset"
var="" ; is_set var && echo "var is set" # shows "var is set"
var="OK" ; is_set var && echo "var is set" # shows "var is set"
Related
Test for non-zero length string in Bash: [ -n “$var” ] or [ “$var” ]

If statement with multiple null string comparisons, print error with the ones that fail

Let's say I have the following if statement that checks multiple variables
if [[ -z ${var1} || -z ${var2} || -z ${var3} ]]; then
echo "Error, one or more variables are empty"
exit 2
fi
I'm looking for a way in which I can do the same test, but somehow be able to print the exact variables that are empty, something like this:
if [[ -z ${var1} || -z ${var2} || -z ${var3} ]]; then
echo "Error, the following variables are empty: ${var1} and ${var3}"
exit 2
fi
Of course, I could test them individually:
if [[ -z ${var1} ]]; then
echo "Error, the following variable is empty: ${var1}"
exit 2
fi
if [[ -z ${var2} ]]; then
echo "Error, the following variable is empty: ${var2}"
exit 2
fi
if [[ -z ${var3} ]]; then
echo "Error, the following variable is empty: ${var3}"
exit 2
fi
But this method also has a big flaw: if there are multiple empty variables, the script will exit after the first one, you'll never know that others are also empty.
One possibility, using indirect expansion:
empty=()
for i in var1 var2 var3; do
[[ -z ${!i} ]] && empty+=( "$i" )
done
if ((${#empty[#]})); then
echo "Error, the following variables are empty: ${empty[*]}"
exit 2
fi
The advantage of the loop that checks for variables is that you don't have to duplicate code.
Instead of executing exit 2 immediately, set a flag (say error=1) and then add the following after the third if:
[[ $error ]] && exit 2
You could also use a loop instead of manually repeating all these ifs and echos:
for varname in var1 var2 var3
do
if [[ -z "${!varname}" ]]
then
echo "Error, the following variable is empty: $varname"
error=1
fi
done
[[ $error ]] && exit 2
You could combine your code pieces like this:
if [[ -z ${var1} ]]; then
echo "Error, the following variable is empty: ${var1}"
fi
if [[ -z ${var2} ]]; then
echo "Error, the following variable is empty: ${var2}"
fi
if [[ -z ${var3} ]]; then
echo "Error, the following variable is empty: ${var3}"
fi
if [[ -z ${var1} || -z ${var2} || -z ${var3} ]]; then
exit 2
fi

existing empty null variable in bash

I am using the code below to determine if a variable in bash exists, if it is empty, or if it has length>0. The code works, but I can't find a good explanation for how if [ -n "${emptyvar+1}" ] can detect if emptyvar is not set. If I remove the +1 then the test fails for "". What is the purpose of the +1 in the test?
#!/bin/bash
emptyvar="a"
if [ -n "${emptyvar+1}" ]
then
echo "emptyvar is defined"
if [[ -z $emptyvar ]]
then
echo "emptyvar is empty";
else
echo "emptyvar is NOT empty";
if [[ -n $emptyvar ]]
then
echo "emptyvar has length > 0";
else
echo "emptyvar has length 0";
fi
fi
else
echo "emptyvar is not defined"
fi
From the bash documentation of Shell Parameter Expansion:
${parameter:+word} If parameter is null or unset, nothing is
substituted, otherwise the expansion of word is substituted.
Omitting the colon (:) makes it test only if the variable is unset, rather than null or unset.
So ${emptyvar+1} tests if $emptyvar is unset. If it is, it expands to the empty string; if not, it expands to 1.
You can also create sets of functions to test a variable passed by its name:
function is_var_set {
[[ -n ${!1+.} ]]
}
function is_var_empty {
[[ -z ${!1} ]]
}
Test:
> A=''
> is_var_set A && echo "A is set." || echo "A is unset."
A is set.
> is_var_empty A && echo "A is empty." || echo "A is not empty."
A is empty.
bash 4.2 added a new test operator, -v, that tests if a variable has been set.
# -v takes the name of the variable, not its values (since you are
# testing if it has a value or not).
if [[ -v emptyvar ]]; then
echo "emptyvar is defined"
if [[ -z $emptyvar ]]
then
echo "emptyvar is empty";
else
echo "emptyvar is NOT empty";
fi
else
echo "emptyvar is not defined"
fi
Note that an empty variable is one whose string has length 0, so your -n test is redundant.
Another way to express it:
$ unset var
$ if [[ -z ${var+x} ]]; then echo unset; elif [[ -z $var ]]; then echo empty; else echo not empty; fi
unset
$ var=
$ if [[ -z ${var+x} ]]; then echo unset; elif [[ -z $var ]]; then echo empty; else echo not empty; fi
empty
$ var=foo
$ if [[ -z ${var+x} ]]; then echo unset; elif [[ -z $var ]]; then echo empty; else echo not empty; fi
not empty
You'll never get "emptyvar has length 0" -- that's what "empty" is

Using unset vs. setting a variable to empty

I'm currently writing a bash testing framework, where in a test function, both standard bash tests ([[) as well as predefined matchers can be used. Matchers are wrappers to '[[' and besides returning a return code, set some meaningful message saying what was expected.
Example:
string_equals() {
if [[ ! $1 = $2 ]]; then
error_message="Expected '$1' to be '$2'."
return 1
fi
}
So, when a matcher is used, and it fails, only then an error_message is set.
Now, at some point later, I test whether the tests succeeded. If it succeeded, I print the expectation in green, if it failed in red.
Furthermore, there may be an error_message set, so I test if a message exists, print it, and then unset it (because the following test may not set an error_message):
if [[ $error_message ]]; then
printf '%s\n' "$error_message"
unset -v error_message
fi
Now my question is, if it is better to unset the variable, or to just set it to '', like
error_message=''
Which one is better? Does it actually make a difference? Or maybe should I have an additional flag indicating that the message was set?
Mostly you don't see a difference, unless you are using set -u:
/home/user1> var=""
/home/user1> echo $var
/home/user1> set -u
/home/user1> echo $var
/home/user1> unset var
/home/user1> echo $var
-bash: var: unbound variable
So really, it depends on how you are going to test the variable.
I will add that my preferred way of testing if it is set is:
[[ -n $var ]] # True if the length of $var is non-zero
or
[[ -z $var ]] # True if zero length
As has been said, using unset is different with arrays as well
$ foo=(4 5 6)
$ foo[2]=
$ echo ${#foo[*]}
3
$ unset foo[2]
$ echo ${#foo[*]}
2
So, by unset'ting the array index 2, you essentially remove that element in the array and decrement the array size (?).
I made my own test..
foo=(5 6 8)
echo ${#foo[*]}
unset foo
echo ${#foo[*]}
Which results in..
3
0
So just to clarify that unset'ting the entire array will in fact remove it entirely.
Based on the comments above, here is a simple test:
isunset() { [[ "${!1}" != 'x' ]] && [[ "${!1-x}" == 'x' ]] && echo 1; }
isset() { [ -z "$(isunset "$1")" ] && echo 1; }
Example:
$ unset foo; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's unset
$ foo= ; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's set
$ foo=bar ; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's set

How to find whether or not a variable is empty in Bash

How can I check if a variable is empty in Bash?
In Bash at least the following command tests if $var is empty:
if [[ -z "$var" ]]; then
# $var is empty, do what you want
fi
The command man test is your friend.
Presuming Bash:
var=""
if [ -n "$var" ]; then
echo "not empty"
else
echo "empty"
fi
I have also seen
if [ "x$variable" = "x" ]; then ...
which is obviously very robust and shell independent.
Also, there is a difference between "empty" and "unset". See How to tell if a string is not defined in a Bash shell script.
if [ ${foo:+1} ]
then
echo "yes"
fi
prints yes if the variable is set. ${foo:+1} will return 1 when the variable is set, otherwise it will return empty string.
[ "$variable" ] || echo empty
: ${variable="value_to_set_if_unset"}
if [[ "$variable" == "" ]] ...
The question asks how to check if a variable is an empty string and the best answers are already given for that.
But I landed here after a period passed programming in PHP, and I was actually searching for a check like the empty function in PHP working in a Bash shell.
After reading the answers I realized I was not thinking properly in Bash, but anyhow in that moment a function like empty in PHP would have been soooo handy in my Bash code.
As I think this can happen to others, I decided to convert the PHP empty function in Bash.
According to the PHP manual:
a variable is considered empty if it doesn't exist or if its value is one of the following:
"" (an empty string)
0 (0 as an integer)
0.0 (0 as a float)
"0" (0 as a string)
an empty array
a variable declared, but without a value
Of course the null and false cases cannot be converted in bash, so they are omitted.
function empty
{
local var="$1"
# Return true if:
# 1. var is a null string ("" as empty string)
# 2. a non set variable is passed
# 3. a declared variable or array but without a value is passed
# 4. an empty array is passed
if test -z "$var"
then
[[ $( echo "1" ) ]]
return
# Return true if var is zero (0 as an integer or "0" as a string)
elif [ "$var" == 0 2> /dev/null ]
then
[[ $( echo "1" ) ]]
return
# Return true if var is 0.0 (0 as a float)
elif [ "$var" == 0.0 2> /dev/null ]
then
[[ $( echo "1" ) ]]
return
fi
[[ $( echo "" ) ]]
}
Example of usage:
if empty "${var}"
then
echo "empty"
else
echo "not empty"
fi
Demo:
The following snippet:
#!/bin/bash
vars=(
""
0
0.0
"0"
1
"string"
" "
)
for (( i=0; i<${#vars[#]}; i++ ))
do
var="${vars[$i]}"
if empty "${var}"
then
what="empty"
else
what="not empty"
fi
echo "VAR \"$var\" is $what"
done
exit
outputs:
VAR "" is empty
VAR "0" is empty
VAR "0.0" is empty
VAR "0" is empty
VAR "1" is not empty
VAR "string" is not empty
VAR " " is not empty
Having said that in a Bash logic the checks on zero in this function can cause side problems imho, anyone using this function should evaluate this risk and maybe decide to cut those checks off leaving only the first one.
This will return true if a variable is unset or set to the empty string ("").
if [ -z "$MyVar" ]
then
echo "The variable MyVar has nothing in it."
elif ! [ -z "$MyVar" ]
then
echo "The variable MyVar has something in it."
fi
You may want to distinguish between unset variables and variables that are set and empty:
is_empty() {
local var_name="$1"
local var_value="${!var_name}"
if [[ -v "$var_name" ]]; then
if [[ -n "$var_value" ]]; then
echo "set and non-empty"
else
echo "set and empty"
fi
else
echo "unset"
fi
}
str="foo"
empty=""
is_empty str
is_empty empty
is_empty none
Result:
set and non-empty
set and empty
unset
BTW, I recommend using set -u which will cause an error when reading unset variables, this can save you from disasters such as
rm -rf $dir
You can read about this and other best practices for a "strict mode" here.
To check if variable v is not set
if [ "$v" == "" ]; then
echo "v not set"
fi

Resources