Usually i only use [[ for all kinds of test cases, because it's the most advanced way and it's more safe to use (Regex, ...).
I know that [[ executes different code than [, but according to the manpage and various documentations, it should at least handle options like "-n" the same way, but it doesn't.
-n STRING the length of STRING is nonzero
VAR=
if [[ -n $VAR ]]
then
echo "\$VAR is nonzero"
else
echo "\$VAR is zero"
fi
$VAR is zero
VAR=
if [ -n $VAR ]
then
echo "\$VAR is nonzero"
else
echo "\$VAR is zero"
fi
$VAR is nonzero
How is this even possible?
bash 4.1.2(1)
I think that your problem is related to quotes.
When you use [ -n $VAR ] the command that is executed won't contain any argument where $VAR should be:
$ set -x
$ [ -n $VAR ]
+ '[' -n ']'
This means that you are essentially testing whether the string -n is non-empty, because the following two tests are equivalent:
[ string ] # is a shorthand for
[ -n string ] # which is always true!
If you use quotes, then you get different behaviour:
$ [ -n "$VAR" ]
+ '[' -n '' ']'
Now you are testing whether the variable is non-empty, so you get the expected behaviour.
Quoting. You have to quote variables that you use in [:
$ VAR=
$ [ -n $VAR ]
$ echo $?
0
$ [ -n "$VAR" ]
$ echo $?
1
Related
I am trying to check out test by using the sytax with the brackets [ and ].
The manpage of test says that -n can be used to check if the length of string is not zero:
-n STRING
the length of STRING is nonzero
In the opposite -z can be used ti check if the length of a string is zero:
-z STRING
the length of STRING is zero
To understand how test works I crated an example with an empty variable foo which has the length of zero and a second variable bar with a length of one:
$ export foo
$ export bar=1
$ [ -n $bar ] ; echo $?
0
$ [ -n $foo ] ; echo $?
0
$ [ -z $bar ] ; echo $?
1
$ [ -z $foo ] ; echo $?
0
The first two tests with the -n are showing both zero as result. I suggested It would be zero for the first test and one for the second. The second test shows the result as suggested.
Try the following:
[ -n "$foo" ] ; echo $?
Since the $foo is an empty string, the
[ -n $foo ] # without quotes
becomes:
[ -n ]
Manual test(1) says:
STRING equivalent to -n STRING
So [ -n ] is treated not like [ -n "" ], but like [ "-n" ]
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.
I found the following command:
var=""; [ -n $var ] && echo T || echo F
This command returns F.
In my understanding, $var is null so the command should return T.
I also checked this:
[ -n "" ] && echo T || echo F
This returns T as I expected.
I can not understand why
[ -n "" ] && echo T || echo F
returns T but
var="" [ -n $var ] && echo T || echo F
returns F?
I checked them on CentOS6.4 bash4.1.2
Executive summary: always quote your parameter expansions unless you know why it should not be. Use [ -n "$var" ] instead.
When unquoted,
[ -n $var ]
expands to
[ -n ]
that is, the empty string is removed from the input and bash is left with a one-argument call to [, which means the command returns true if the argument is non-empty. -n here is not an operator; it is simply a non-empty string.
You need to quote the parameter expansion so that
[ -n "$var" ]
expands to
[ -n "" ]
which is a two-argument call to [, which will do as you expect: test if the second argument has non-zero length.
This difference in output in case when you use a variable is that you don't quote the variable.
When you say:
[ -n $var ] && echo T || echo F
the shell reads it as:
'[' -n ']'
and returns T. This case is equivalent to saying [ foo ] which would always be true.
On the other hand, when you say:
[ -n "" ] && echo T || echo F
the shell would read it as:
'[' -n '' ']'
and return F.
The Bash Reference Manual says that
[ string ]
and
[ -n string ]
will both return true if the string 's length is not 0
but the fact is not as so
greet=
if [ $greet ]; then
echo '1'
else
echo '2'
fi
if [ -n $greet ]; then
echo '1'
else
echo '2'
fi
the output is
2
1
the Bash Reference Manual just says
-n string
string
True if the length of string is non-zero.
so, what the real difference between the two form?
As #user1502952 said, you need to use double-quotes; but let me explain why. Suppose you execute:
greet=
[ -n $greet ] && echo "it's nonblank"
When the shell parses the [ -n $greet ] part, it expands $greet to the empty string, and then does word splitting. For instance, if $greet expanded to something with spaces in the middle, it would treat each "word" as a separate argument to the [ command. In this case, however, $greet expands to nothing, which contains no "word"s at all, and hence is treated as zero arguments to [ -- it effectively vanishes from the command. So [ -n $greet ] is equivalent to [ -n ], which checks to see if the string "-n" is nonblank. It is, so it evaluates to true.
Compare this with [ -n "$greet" ]: in this case, the double-quotes allow the expansion of $greet, but prevent word splitting. So the [ command actually gets a zero-length second argument, realizes that -n is supposed to be an operator, and gets the expected answer.
when you are using -n option, it is required to use double quotes.
if [ -n "$greet" ]
as the string is empty the above expression evaluates to false, as the length is zero.
if [ "$greet" ]
this also evaluates to false as the string is empty.
Moreover to check for empty string, -z option can be used.
if [ -z "$greet" ]
this will be true as the string is empty.
Check this link too: http://tldp.org/LDP/abs/html/comparison-ops.html
Bash performs word splitting inside [ but not inside [[, so you don't have to quote parameters if you use [[:
$ x=
$ [ -n $x ]; echo $?; [ -n "$x" ]; echo $?
0
1
$ [[ -n $x ]]; echo $?; [[ -n "$x" ]]; echo $?
1
1
type shows [[ $x ]] as [[ -n $x ]]:
$ f() { [[ $x ]]; }; type f
f is a function
f ()
{
[[ -n $x ]]
}
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.)