unset variable check in bash - bash

I have two variables declared but unset:
__var1=
__var2=
Now I set __var2 to have some value:
__var2=1
When I try to do a check like this:
[ -z "$__var1" -a -z "$__var2" ] || echo "Both missing!"
I am getting that message Both missing!. But that's incorrect.
Why is that? And how to do a proper check, to see if both of them are missing?

And if the user wants to check if the variable is really unset and not just having an empty value, you can do:
$ A=1234
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ A=
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ unset A
$ [[ -z ${A+.} ]] && echo "Variable is unset."
Variable is unset.
In which in your case it could be
[[ -z ${__var1+.} && -z ${__var2+.} ]] && echo "Both variables are unset!"

#Dave Schweissguth's answer makes a good point about the logic of your code, but there are also things to observe about the syntax:
[Update: The original form of the question used assignments such as $__var1= - this has since been corrected] In Bourne-like/POSIX-compatible shells you do not use the $ prefix when assigning a value, only when referencing it; thus, your assignments should read:
__var1=
__var2= # or, later: __var2=1
Your question is tagged bash, so the best bash way to write your could would be:
[[ -z $__var1 && -z $__var2 ]] && echo "Both missing!"
Note the use of [[ ... ]] rater than [ ... ], which obviates the need to double-quote the operands to -z.
By contrast, the most portable (POSIX-compliant) way is:
[ -z "$__var1" ] && [ -z "$__var2" ] && echo "Both missing!"

Your code prints "Both missing!" if it's not true (||) that both (-a) variables are empty (-z). You want to print the message if that IS true. Do that like this:
[ -z "$__var1" -a -z "$__var2" ] && echo "Both missing!"
I don't recall ever seeing a version of bash or test (what sh uses to evaluate the same expressions) without -z or -a, so as far as I know the above will work on any Unix-like system you're likely to find.

Related

meaning of -a -z in if [ -z "$ENV_VAR" -a -z "$ENV_VAR2"] bash conditional [duplicate]

This question already has answers here:
Is there a list of 'if' switches anywhere?
(5 answers)
Closed 4 years ago.
What is is meaning of -a -z in
if [ -z "$ENV_VAR" -a -z "$ENV_VAR2"]; then
...
fi
bash conditional?
The first -z checks if $ENV_VAR defined according to
-z string True if the length of string is zero.
What does -a -z combination test with relation to ENV_VAR2?
according to the docs
-a file True if file exists.
however, ENV_VAR2 may contain text only, not a file name.
[ -z "$ENV_VAR" -a -z "$ENV_VAR2" ] has 2 conditions ANDed together using -a switch:
What it means is this:
-z "$ENV_VAR": $ENV_VAR is empty
-a: and
-z "$ENV_VAR2": $ENV_VAR2 is empty
btw if you're using bash you can refactor this condition to make it bit more succinct:
[[ -z $ENV_VAR && -z $ENV_VAR2 ]]
Please try this "man test".
Ideally, in that output, you'll see that -a performs an "AND" between two expressions.
It's "and".
See man test
EXPRESSION1 -a EXPRESSION2
both EXPRESSION1 and EXPRESSION2 are true
Examples:
$ [ -z "" -a -z "" ] && echo Hello
Hello
$ [[ -z "" -a -z "" ]] && echo Hello
bash: syntax error in conditional expression
bash: syntax error near `-a'
If used with single [ it is the "and" from test. If used with [[ it is the file check from bash.
The bash solution:
$ [[ -z "" && -z "" ]] && echo Hello
Hello
For POSIX compatibility, [[ ... && ... ]] is not available, but -a is considered obsolete (and optional) by POSIX, so use two separate [ commands instead.
if [ -z "$ENV_VAR" ] && [ -z "$ENV_VAR2" ]; then
...
fi

basic shell bash checking/switch arguments

This is a beginner question, I have already checked that Check existence of input argument in a Bash shell script but it doesn't fully explain what I want to do.
gcc -Wall cx17.$1.c -o cx17.$1
if [ -z "$1" ]
then
echo "No argument supplied"
else if [ -z "$2"]
then
echo "Data file is missing!!"
else if [ -z "$3"]
then
./cx17.$1 $2 > ./cx17.$1.$2
else
./cx17.$1 $2 $3 > ./cx17.$1.$2
fi
So you understand this very basic use case, depending on arguments (if there is 1, 2 or 3) the script will perform a different task.
I know it's really simple that's why I think I'm missing something obvious.
Thanks for your help
The answered I validate gave me some errors but lead me to the right stuff:
if [ -z "$1" ]; then
echo 'No argument supplied';
elif [ -z "$2" ]; then
echo 'Data file is missing!!';
elif [ -z "$3" ]; then
./cx17.$1 $2 >./cx17.$1.$2;
else
./cx17.$1 $2 $3 >./cx17.$1.$2;
fi;
Replace else if with elif:
if [[ -z "$1" ]]; then
echo 'No argument supplied';
elif [[ -z "$2" ]]; then
echo 'Data file is missing!!';
elif [[ -z "$3" ]]; then
"./cx17.$1" "$2" >"./cx17.$1.$2";
else
"./cx17.$1" "$2" "$3" >"./cx17.$1.$2";
fi;
Other recommendations:
Always double-quote words that contain variable substitutions, otherwise word splitting and shell globbing can take effect on the expanded variable content.
Always use [[ instead of [, since the former is more powerful, and it's good to be consistent.
If interpolation is not required, use single-quotes rather than double-quotes, since single-quotes do not interpolate anything; it's just safer that way.
You can dispense with the if statement altogether using the ${var:?msg} construct, which will exit the script if the given variable doesn't have a non-null value.
: ${1:?No argument given}
: ${2:?Data file is missing!}
# $1 and $2 guaranteed to be non-null; the program
# will receive 1 or 2 arguments, depending on how many
# arguments are present in $#
./cx17."$1" "${#:2:2}" > "./cx17.$1.$2"

Multiple `if` statements in bash script

I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi

bash if with or and negation

why does:
#!/bin/bash
wtf=false
if [ $wtf ] || [ ! -f filethatexists.whatever ]
then
echo "WTF1"
fi
if [ ! -f filethatexists.whatever ]
then
echo "WTF2"
fi
print:
WTF1
instead of nothing? It is especially perplexing that the second form works as expected and the first not.
The basic test
[ $wtf ]
tests whether the string in the middle is empty or not.
Since $wtf contains the string 'false', the test returns true, or exit status 0 for success, because 'false' is not the same as the empty string '' — and hence you get WTF1 as the response.
Try with:
wtf=''
As pointed out by Gordon Davisson (and Dennis Williamson), it is a good idea to be careful with strings that you are testing. Indeed, I should have stated that I would always use [ -n "$wtf" ] or [ -z "$wtf" ] to test whether a variable is set, because that was necessary when I was learning shell, once upon a quarter century ago. I've had counter stories from Bash afficionados that you don't have to worry about it in Bash - however, I think the code here provides a counter-example that in fact you do still have to worry about it.
So, some best practices:
Enclose tested variables in double quotes, or
(In Bash), use [[ $wtf ]] which does know how to handle the variable expansion.
Use the -n or -z tests to test for non-empty or empty values.
There can be exceptions to the rules - but you will not go far wrong following them.
Consider the code:
wtf="1 -eq 0"
[ $wtf ] && echo "WTF0"
[[ $wtf ]] && echo "WTF1"
wtf="false"
[ $wtf ] && echo "WTF2"
[[ $wtf ]] && echo "WTF3"
wtf=""
[ $wtf ] && echo "WTF4"
[[ $wtf ]] && echo "WTF5"
wtf="false"
[ "$wtf" ] && echo "WTF6"
[[ "$wtf" ]] && echo "WTF7"
wtf=""
[ "$wtf" ] && echo "WTF8"
[[ "$wtf" ]] && echo "WTF9"
That produces:
WTF1
WTF2
WTF3
WTF6
WTF7
with both bash and ksh (as found on MacOS X 10.6.4, when run with 'bash testcode.sh' or 'ksh testcode.sh'). A real Bourne shell (if you can still find such a thing) would object to the double-bracket operations - it would not be able to find the command '[[' on $PATH.
You can extend the testing to cover more cases ad nauseam.
Here's a handy little trick:
wtf=false
if $wtf || [ ! -f filethatexists.whatever ]
In this form, the contents of the variable are executed and the return value determines whether the test passes or fails. It happens that true and false are Bash builtins that return the appropriate value.
if [ $wtf = true ] || [ ! -f . .

Escaping in test comparisons

In the following, I would like check if a given variable name is set:
$ set hello
$ echo $1
hello
$ echo $hello
$ [[ -z \$$1 ]] && echo true || echo false
false
Since $hello is unset, I would expect the test to return true. What's wrong here? I would assume I am escaping the dollar incorrectly.
TYIA
You are testing if \$$1 is empty. Since it begins with a $, it is not empty. In fact, \$$1 expands to the string $hello.
You need to tell the shell that you want to treat the value of $1 as the name of a parameter to expand.
With bash: [[ -z ${!1} ]]
With zsh: [[ -z ${(P)1} ]]
With ksh: tmp=$1; typeset -n tmp; [[ -z $tmp ]]
Portably: eval "tmp=\$$1"; [ -z "$tmp" ]
(Note that these will treat unset and empty identically, which is usually the right thing.)

Resources