Is there any difference between
if [ ! -z "$var" ] then
# do smth
fi
and
if [ "$var" ] then
# do smth
fi
They both seem to check if variable is set
Yes, they're equivalent, but there are a couple of notes that apply to both of them:
You need either a semicolon or a line break between ] and the then keyword, or it'll misparse them weirdly.
They're not testing whether the variable is set, they're testing whether it's set to something other than the empty string (see this question for ways to check whether a variable is truly unset).
However, I actually prefer a third also-equivalent option:
if [ -n "$var" ]; then
I consider this semantically clearer, because the -n operator specifically checks for something being non-empty.
There's no difference between
[ ! -z "$var" ]
[ -n "$var" ]
[ "$var" ]
All of them are true if $var is not empty.
Related
I want to test multiple variables to see if they are empty. So I did the following:
if [ test -z "$VAR1" ] || [ test -z "$VAR2" ]
then
echo "Empty!"
fi
However, it doesn't work. The output is:
[: -z: binary operator expected
[: -z: binary operator expected
What have I done wrong? The code above works fine if I leave out the OR (||) condition.
Use either brackets or the test command, don't use both. Either of the following will work:
if [ -z "$VAR1" ] || [ -z "$VAR2" ]
then
echo "Empty!"
fi
Or:
if test -z "$VAR1" || test -z "$VAR2"
then
echo "Empty!"
fi
In some older shells [ was just an alias for the test command. You could even miss out the closing ] (or add it after test) and everything would be fine. Nowadays, on bash this will give a syntax error. However, the two (correct) syntaxes are still functionally equivalent and can be used interchangeably but not at the same time.
You should either use test, or [; they are synonymous, except [ requires a ] as last argument.
[ -z "$VAR1" ] || [ -z "$VAR2" ]
or
test -z "$VAR1" || test -z "$VAR2"
I am trying to store a condition for comparing values in a variable but it is not working.
The code I have written is :
read op
if [ $op == "desc" ]
then
compare='${arr[0]} -gt ${arr[1]}'
if [ eval "$compare" ]
then
echo "SWAP"
fi
fi
what am I doing wrong?
The right way to write your eval statement is:
if eval [ "$compare" ]
then
...
Don't use variables to store a command - see BashFAQ/050. You can rewrite your code as:
read op
if [ "$op" = "desc" ]; then
if [ "${arr[0]}" -gt "${arr[1]}" ]; then
echo "SWAP"
fi
fi
Notes:
it's important to quote your variables inside [ ] to prevent word splitting and globbing
use shellcheck to validate your code
See also:
Why should eval be avoided in Bash, and what should I use instead?
How to use double or single brackets, parentheses, curly braces
Word Splitting
Given what the code does, eval isn't needed and this would be enough:
read op ; [ "$op" = "desc" ] && [ "${arr[0]}" -gt "${arr[1]}" ] && echo "SWAP"
(The above is functionally equivalent to codeforester's code.)
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.
How to make sure a variable is not empty with the -z option ?
errorstatus="notnull"
if [ !-z $errorstatus ]
then
echo "string is not null"
fi
It returns the error :
./test: line 2: [: !-z: unary operator expected
Of course it does. After replacing the variable, it reads [ !-z ], which is not a valid [ command. Use double quotes, or [[.
if [ ! -z "$errorstatus" ]
if [[ ! -z $errorstatus ]]
Why would you use -z? To test if a string is non-empty, you typically use -n:
if test -n "$errorstatus"; then
echo errorstatus is not empty
fi
I think this is the syntax you are looking for:
if [ -z != $errorstatus ]
then
commands
else
commands
fi
if [ !-z $errorstatus ]
just put a space between ! and -z, like this:
if [ ! -z $errorstatus ]
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 . .