Multiple `if` statements in bash script - bash

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

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

String is simultaneously greater than zero and empty

Would someone please care to explain how on earth this is supposed to make sense:
Contents of insanity.sh:
#!/bin/bash
ARG=""
if [ -n $ARG ]; then
echo string is greater than zero
fi
if [ -z $ARG ]; then
echo string is empty
fi
Running the script:
[USERNAME#login001 clusterUtils]$ ./insanity.sh
string is greater than zero
string is empty
Currently using this tutorial at the moment.
It is happening because you're not quoting $ARG inside [ ... ].
Without quoting your code is effectively running as:
if [ -n ]; then
echo string is greater than zero
fi
if [ -z ]; then
echo string is empty
fi
Any non-empty string between [ ... ] will evaluate to true, hence both if conditions are successful.
Fix: It is recommended to use [[ ... ]] as you're using bash:
arg=""
if [[ -n $arg ]]; then
echo 'string is greater than zero'
fi
if [[ -z $arg ]]; then
echo 'string is equal to zero, empty'
fi
[[ ... ]] doesn't require your variables to be quoted like [ ... ] sine [ is an external command and [[ ... ]] is a builtin bash construct.
Also avoid all uppercase variables in your script to avoid conflicts with reserved env variables.

bash [: too many arguments greater than symbol

This isn't really a question (though I have one at the end), but rather a solution to a problem that I wanted to share in case it helps someone else.
For the longest time I had been getting bash: [: too many arguments when opening a new terminal (specifically iTerm2 on OS X with the bash-completion macport installed). This error originated from the line if [ -n "$BASH_VERSION" -a -n "$PS1" -a -z "$BASH_COMPLETION_COMPAT_DIR" ]; then in the file /opt/local/etc/bash_completion. I have finally tracked down the problem to the fact that I had export PS1='>' in my .bash_profile. Changing PS1 to something else (e.g. '> ') fixes the problem with bash completion.
Some experimenting in OS X and Debian reveals that this problem occurs when adding extra expressions (with -a or -o) into a test ([ ]) after the expression involving '>'. E.g.,
> A='>'; if [ -n "$A" ]; then echo "yes"; fi
yes
> A='>'; if [ -n "$A" -a -n "$A" ]; then echo "yes"; fi
bash: [: too many arguments
> A='> '; if [ -n "$A" -o -n "$A" ]; then echo "yes"; fi
yes
> A='>'; if [ -n "$A" -o -n "Hello" ]; then echo "yes"; fi
bash: [: too many arguments
> A='>'; if [ -n "Hello" -a -n "$A" ]; then echo "yes"; fi
yes
Is this a (known) bug in bash?
Your workaround is effective, as long as the string stored in $A is not an operator that [ / test recognizes - simply adding a space is sufficient, as you've discovered.
Surely the "greater than" should be interpreted as just a string? It works with '> ' after all.
No, the content of $A is not interpreted as just a string. (If you wanted that, you'd have to use [[ instead, which is parsed in a special context, more like you'd expect from traditional programming languages.)
[ (test) is a builtin (also exists as an external utility on most systems) and is therefore parsed with command syntax, which means:
the shell performs its expansions first - $A references are replaced with the content of the variable in this case.
the result is then passed to [
Thus, from the perspective of [, it doesn't matter whether or not the operator it ultimately sees - > in your example - came from a literal or was stored in a variable.
But note that whitespace matters: passing > (no spaces) is interpreted as an operator; >, by contrast, ><space> is not - because that exact literal is more than just the operator.
The bottom line is:
The bash-completion script you're using is not robust.
As #chepner states in a comment on the question, POSIX recommends not using -o / -a to avoid the ambiguity you encountered (emphasis mine):
The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. (Many expressions using them are ambiguously defined by the grammar depending on the specific expressions being evaluated.)
Specifically, using separate [ ... ] expressions joined with && (instead of -a) and || (instead of -o) solves the problem:
[ -n "$BASH_VERSION" ] && [ -n "$PS1" ] && [ -z "$BASH_COMPLETION_COMPAT_DIR" ]
Or, more simply, taking advantage of a non-empty string evaluating to true:
[ "$BASH_VERSION" ] && [ "$PS1" ] && [ -z "$BASH_COMPLETION_COMPAT_DIR" ]
Note that while -a and -o introduce ambiguities, they are not a security concern - you cannot inject arbitrary code through their use.
If you want to use two or more condition you should use
if [ condition1 ] && [condition2 ]
or
if [ condition1 ] || [condition2 ]
so in your case (first if "and"):
A='>'; if [ -n "$A" ] && [ -n "$A" ]; then echo "yes"; fi
for the "or" if:
A='>'; if [ -n "$A" ] || [ -n "Hello" ]; then echo "yes"; fi
But be aware that that the second check [ -n "Hello" ] is always true, so it's better to remove it.
You may be interested in shellcheck to validate your bash script syntax.

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"

unset variable check in 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.

Resources