Testing to see if an env variable is set in bash - bash

In a bash script, I'm trying to test for the existence of a variable. But no matter what I do, my "if" test returns true. Here's the code:
ignored-deps-are-not-set () {
if [ -z "${ignored_deps+x}" ]
then
return 0
fi
return 1
}
ignored_deps=1
ignored-deps-are-not-set
echo "ignored-deps function returns: $?"
if [ ignored-deps-are-not-set ]
then
echo "variable is unset"
else
echo "variable exists"
fi
Here is the output as written:
ignored-deps function returns: 1
variable is unset
And the output when I comment out the line where ignored_deps is set.
ignored-deps function returns: 0
variable is unset
No matter what, it says the variable is unset. What am I missing?

This line:
if [ ignored-deps-are-not-set ]
tests whether the string 'ignored-deps-are-not-set' is empty or not. It returns true because the string is not empty. It does not execute a command (and hence the function).
If you want to test whether a variable is set, use one of the ${variable:xxxx} notations.
if [ ${ignored_deps+x} ]
then echo "ignored_deps is set ($ignored_deps)"
else echo "ignored_deps is not set"
fi
The ${ignored_deps+x} notation evaluates to x if $ignored_deps is set, even if it is set to an empty string. If you only want it set to a non-empty value, then use a colon too:
if [ ${ignored_deps:+x} ]
then echo "ignored_deps is set ($ignored_deps)"
else echo "ignored_deps is not set or is empty"
fi
If you want to execute the function (assuming the dashes work in the function name), then:
if ignored-deps-are-not-set
then echo "Function returned a zero (success) status"
else echo "Function returned a non-zero (failure) status"
fi

You're not actually executing the function:
if ignored-deps-are-not-set; then ...
Withing [] brackets, the literal string "ignored-deps-are-not-set" is seen as true.

if [ ${myvar:-notset} -eq "notset" ] then
...

Yet another way to test for the existence of a variable:
if compgen -A variable test_existence_of_var; then
echo yes
else
echo no
fi

--edit--
just realized that it's a function tha tyou're trying to call, convention is wrong.
See:
Z000DGQD#CND131D5W6 ~
$ function a-b-c() {
> return 1
> }
Z000DGQD#CND131D5W6 ~
$ a-b-c
Z000DGQD#CND131D5W6 ~
$ echo $?
1
Z000DGQD#CND131D5W6 ~
$ if a-b-c; then echo hi; else echo ho; fi
ho
Z000DGQD#CND131D5W6 ~
$ if [ a-b-c ]; then echo hi; else echo ho; fi
hi
Z000DGQD#CND131D5W6 ~
--edit end--
Fix the variable name (see my comment to your post)
then
See Parameter Expansion section in man bash.
${parameter:?word}:
Display Error if Null or Unset. If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.

Related

Bash: `if ! [ $falseSetVar ] ` won't evaluate correctly for me

I have an if statement within a loop. It's set to false initially so I insert a timestamp in a file at the first run of the loop.
I can't seem to get the following to evaluate correctly.
$ConnectionIsCurrently=false
if ! [ $ConnectionIsCurrently ]; then
# changing false to true so this only occurs once.
$ConnectionIsCurrently=true
fi
Here is the full loop:
while [ $i -le $NoOfTests ]; do
ping -c1 -t1 www.google.ie > /dev/null
if [ $? = 0 ]; then
ConTestPASSCount=$((ConTestPASSCount+1))
if ! [ $ConnectionIsCurrently ]; then
printf 'PASSED AT: '
date "+%s"
printf 'PASSED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=true
fi
echo "PASSCount $ConTestPASSCount"
else
ConTestFAILCount=$((ConTestFAILCount+1))
if [ $ConnectionIsCurrently ]; then
printf 'FAILED AT: '
date "+%s"
printf 'FAILED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=false
fi
echo "FAILCount $ConTestFAILCount"
fi
sleep 1
Testcount=$((Testcount+1))
i=$((i+1))
done
The shell doesn't have boolean values, it just operates on strings (or numbers in $(())). The syntax:
if [ $ConnectionIsCurrently ]
tests whether $ConnectionIsCurrently is a non-empty string, and "false" is not empty.
You could use an empty value as falsey, and any non-empty value as truthy.
ConnectionIsCurrently=
if ! [ "$ConnectionIsCurrently" ]; then
ConnectionIsCurrently=true
fi
Note also that you don't put $ before the variable name when you're assigning to it, only when you're reading it. And you should generally quote variables, unless you're sure you want word splitting done. This is especially important when the variable could be empty, as in this case; without the quotes, the [ command doesn't receive any parameter there.
false and true are actually commands (and also bash builtins), so you can run them as commands and act on the exit status:
ConnectionIsCurrently=false
if ! $ConnectionIsCurrently; then
# changing false to true so this only occurs once.
ConnectionIsCurrently=true
fi
The [...] are not required syntax for the if command: [ is just a regular command whose exit status is used by if.
To summarize:
if and while execute a command and branch depending on whether that command succeeds or fails.
false is a command that produces no output and always fails.
true is a command that produces no output and always succeeds.
[ is a command that succeeds or fails depending on the evaluation of the expression preceding the closing ] argument; man test or info test for details. With a single argument (which should be enclosed in double quotes) before the ], [ succeeds if and only if the argument is non-empty. The [ command is typically built into the shell, but it acts like a command; it's not a special shell syntax.
The shell (sh, bash, ksh, zsh) does not have built-in Boolean types or values. There are several common idioms for using Booleans in shell scripts.
A. Assign a variable the string value true or false. Using such a value in an if statement will do the right thing. (This method is my personal favorite.) Note that the strings true and false are the names of commands, not arbitrary strings.
foo=true
if $foo ; then echo OK ; else echo Oops ; fi
B. Assign a variable any arbitrary non-empty value for truthiness, or the empty string (or leave it unset) for falsitude:
foo=yes
if [ "$foo" ] ; then echo OK ; else echo Oops ; fi
foo=""
if [ "$foo" ] ; then echo Oops ; else echo OK ; fi
(The shell treats an unset variable as if it were set to the empty string -- unless you've done set -o nounset, but that's not usually done in scripts.)
C. Pick two arbitrary strings to represent truth and falsehood, and use them consistently. Use string comparisons to test.
foo=TRUE
if [ "$foo" = TRUE ] ; then echo OK ; else echo Oops ; fi
foo=FALSE
if [ "$foo" = TRUE ] ; then echo Oops ; else echo OK ; fi
All of these methods are potentially error-prone. If you forget a $ or misspell one of your conventional strings, you can get bad results with no warning from the shell; for example with method C, the string True will silently be treated as a false condition. Languages with strictly behaving Booleans can avoid these problems. Bash is not such a language.

`set -u` (nounset) vs checking whether I have arguments

I'm trying to improve this nasty old script. I found an undefined variable error to fix, so I added set -u to catch any similar errors.
I get an undefined variable error for "$1", because of this code
if [ -z "$1" ]; then
process "$command"
It just wants to know if there are arguments or not. (The behaviour when passed an empty string as the first argument is not intended. It won't be a problem if we happen to fix that as well).
What's a good way to check whether we have arguments, when running with set -u?
The code above won't work if we replace "$1" with "$#", because of the special way "$#" is expanded when there is more than one argument.
$# contains the number of arguments, so you can test for $1, $2, etc. to exist before accessing them.
if (( $# == 0 )); then
# no arguments
else
# have arguments
fi;
You can ignore the automatic exit due to set -u by setting a default value in the parameter expansion:
#!/bin/sh
set -u
if [ -z "${1-}" ] ; then
echo "\$1 not set or empty"
exit 1
fi
echo "$2" # this will crash if $2 is unset
The syntax is ${parameter-default}, which gives the string default if the named parameter is unset, and the value of parameter otherwise. Similarly, ${parameter:-default} gives default if the named parameter is unset or empty. Above, we just used an empty default value. (${1:-} would be the same here, since we'd just change an empty value to an empty value.)
That's a feature of the POSIX shell and works with other variables too, not just the positional parameters.
If you want to tell the difference between an unset variable and an empty value, use ${par+x}:
if [ "${1+x}" != x ] ; then
echo "\$1 is not set"
fi
My personal favorite :
if
(($#))
then
# We have at least one argument
fi
Or :
if
((!$#))
then
# We have no argument
fi
If each positional argument has a fixed meaning, you can also use this construct:
: ${1:?Missing first argument}
If the first positional argument isn't set, the shell will print "Missing first argument" as an error message and exit. Otherwise, the rest of the script can continue, safe in the knowledge the $1 does, indeed, have a non-empty value.
Use $#, the number of arguments. This provides the most consistent handling for empty arguments.
You might also see the use of "$*". It is similar to "$#", but it is expanded differently when there are multiple arguments.
a() {
for i in "$#"; do echo $i; done
}
b() {
for i in "$*"; do echo $i; done
}
c() {
echo $#
}
echo "a()"
a "1 2" 3
echo "b()"
b "1 2" 3
echo "c()"
c "1 2" 3
# Result:
a()
1 2
3
b()
1 2 3
c()
2

Bash function to check for unset and blank variables

I'm writing a bash function to check for blank or unset variables.
function exit_if_var_does_not_exist {
TMP_NAME=$1
TMP_VALUE=`echo $TMP_NAME`
if [ -z ${TMP_VALUE+x} ]; then
echo "Variable [${TMP_NAME}] is unset. Exiting."
exit 1
else
if [ ${TMP_VALUE} = "" ]; then
echo "Variable [${TMP_NAME}] is set to ''. Exiting."
exit 1
fi
fi
echo "Variable [${TMP_NAME}] is set to ${TMP_VALUE}"
}
VAR=Test
exit_if_var_does_not_exist VAR
BLANK=
exit_if_var_does_not_exist BLANK
This does not give me the expected output in TMP_VALUE. Can someone please help me with what I'm missing here?
-bash-3.2$ ./x.sh
Variable [VAR] is set to VAR
Variable [BLANK] is set to BLANK
The problem with the empty test block is that at no point in this snippet do you ever actually get the value of the originally named variable.
When you use TMP_NAME=$1 you assign the name of the input variable to TMP_NAME and then
TMP_VALUE=`echo $TMP_NAME`
just assigns that name to TMP_VALUE. Effectively you just ran TMP_VALUE=$1.
So you aren't testing for whether the originally named variable has contents anywhere here.
To do that you need to use indirect expansion.
Namely TMP_VALUE=${!TMP_NAME} or TMP_VALUE=${!1}.
Side comment your "unset" test block at the top can never trigger.
TMP_VALUE can never be unset because you assign to it. Even TMP_VALUE= marks the variable as "set". So that bit is useless. Though, and thank David C. Rankin for trying long enough to make me think of this, you can use an indirect expansion trick to make this work.
[ -z "${!TMP_NAME+x}" ] will return true for any set variable named in TMP_NAME and false for an unset variable.
That all said if what you want to do is error if a variable is unset or blank the shell has you covered. Just use
: "${VAR:?Error VAR is unset or blank.}" || exit 1
Finally, as David C. Rankin points out inside [ you need to quote expansions that might disappear when they change the meaning of tests (you see this in the -z test above) as well as here.
So [ ${TMP_VALUE} = "" ] needs to be [ "${TMP_VALUE}" = "" ] because is TMP_VALUE is empty the first version would become a syntax error because [ = "" ] isn't a valid invocation of test/[.
I'm not sure this will work for all cases, but try:
#!/bin/bash
function exit_if_var_does_not_exist {
local TMP_NAME=$1
local TMP_VALUE="${!TMP_NAME}"
[ -z "${!TMP_NAME}" ] && {
echo "Variable [${TMP_NAME}] is unset. Exiting."
return 1
}
[ "$TMP_VALUE" = "" ] && {
echo "Variable [${TMP_NAME}] is set to ''. Exiting."
return 1
}
echo "Variable [${TMP_NAME}] is set to ${TMP_VALUE}"
}
VAR=Test
exit_if_var_does_not_exist VAR
# BLANK=
exit_if_var_does_not_exist BLANK
Output
$ bash tstempty.sh
Variable [VAR] is set to Test
Variable [BLANK] is unset. Exiting.
Improved Indirection Soup
After a bit of discussion in the comments and a suggestion, I think I have a version that works more consistently. Again, I qualify this with I am not sure it will work in all situations, but for the testing it seems to:
#!/bin/bash
function exit_if_var_does_not_exist {
local TMP_NAME=$1
local TMP_VALUE="${!TMP_NAME}"
if ! declare -p $1 >/dev/null 2>&1 ; then
[ -z "${!TMP_NAME}" ] && {
echo "Variable [${TMP_NAME}] is unset. Exiting."
return 1
}
elif [ "$TMP_VALUE" = "" ]; then
echo "Variable [${TMP_NAME}] is set to ''. Exiting."
return 1
fi
echo "Variable [${TMP_NAME}] is set to ${TMP_VALUE}"
}
VAR=Test
exit_if_var_does_not_exist VAR
# BLANK=
exit_if_var_does_not_exist BLANK
EMPTY=""
exit_if_var_does_not_exist EMPTY
Output
$ bash tstempty.sh
Variable [VAR] is set to Test
Variable [BLANK] is unset. Exiting.
Variable [EMPTY] is set to ''. Exiting.
Another way:
exit_if_var_does_not_exist() {
if ! (set -u; : "${!1}") >/dev/null 2>&1; then
printf 'Variable [%s] is unset. Exiting.\n' "$1"
exit 1
elif [ -z "${!1}" ]; then
printf "Variable [%s] is set to ''. Exiting.\n" "$1"
exit 1
fi
printf 'Variable [%s] is set to %s\n' "$1" "${!1}"
}
If you don't need to separate the two error messages:
exit_if_var_does_not_exist() {
: "${!1:?$1 is unset or blank}"
printf 'Variable [%s] is set to %s\n' "$1" "${!1}"
}

using if and a boolean function in bash script: if condition evaluates to false when function returns true

is_dir_empty(){
for file in "$1"
do
if [ "$file" != "$1" ]; then
return 0
fi
done
echo "return 1"
return 1
}
file="/home/tmp/*.sh"
if is_dir_empty "$file"; then
echo "empty"
else echo "not empty"
fi
it outputs
return 1
not empty
so is_dir_empty returned 1 but if condition evaluated to false somehow.... why?
Because shell scripts follow the Unix convention of expecting utilities to return zero for success and non-zero for failure, so boolean conditions are inverted.
Globs are not expanded in double quotes, so you're always comparing against the literal value /home/tmp/*.sh. Unquote $1 in the for loop, and it'll word split and glob expand into a list of .sh files (this online tool would have pointed this out automatically).
Also, unlike in C, zero is considered success and non-zero failure.
you can check if a directory is empty by:
[ "$(ls -A /path/to/directory)" ] && echo "Not Empty" || echo "Empty"

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