'&&' vs. '&' with the 'test' command in Bash
The meaning of &&
and &
are intrinsically different.
- What is
&&
in Bash? In Bash—and many other programming languages—&&
means “AND”. And in command execution context like this, it means items to the left as well as right of&&
should be run in sequence in this case. - What is
&
in Bash? And a single&
means that the preceding commands—to the immediate left of the&
—should simply be run in the background.
So looking at your example:
gndlp@ubuntu:~$ test -x examples.desktop && echo $?
gndlp@ubuntu:~$ test -x examples.desktop & echo $?
[1] 2992
0
The first command—as it is structured—actually does not return anything. But second command returns a [1] 2992
in which the 2992
refers to the process ID (PID) that is running in the background and the 0
is the output of the first command.
Since the second command is just running test -x examples.desktop
in the background it happens quite quickly, so the process ID is spawned and gone pretty immediately.
How to use 'AND' condition in 'test' command in bash shell
Test bash command [[
is different from [
. In [[
-a file True if file exists.
. Try &&
for AND bool operator...
But the syntax is a bit different. =~
means regex pattern matching and the "
not needed around env vars and regex expression.
So this works for me:
[[ $string1 =~ not && ! $string1 =~ $chas ]] && echo "Hello"
Trying to understand behavior of `test command` in Bash
test
is a Bash built-in, often invoked by the alternative name [
.
The last command (test ./a.out
) exits with status 0 indicating success because test ./a.out
checks whether ./a.out
as a string has one or more characters in it (is not an empty string), and because it isn't an empty string, returns success or 0. The test ./a.out
command line does not execute your a.out
program — as you could see by printing something from within your program.
As written, your program doesn't need the <stdio.h>
header or the arguments to main()
— it should be int main(void)
. You could lose <stdlib.h>
too if you use return 1;
instead of exit(1);
:
int main(void)
{
return 1;
}
To use the exit status in an if
condition in the shell, just use it directly:
if ./a.out ; then
echo Success
else
echo Failure
fi
Rule of Thumb: Don't call C programs test
because you will be confused sooner or later — usually sooner rather than later.
Bash test command operators
The mnemonic for -ne
is 'not equal'; it does an arithmetic comparison on the two values for inequality.
The mnemonic for -n
is 'not empty'; it tests whether the argument ("$1"
in this case) is an empty string. If $1
is defined and has a value other than the empty string, the test will be true.
See Bash conditional expressions for more details.
The test
command, also known as [
, supports the other numeric comparison operators too: -lt
(less than), -le
(less than or equal to), -gt
(greater than), -ge
(greater than or equal to), and -eq
(equal). The -z
operator tests for a zero length string — but note that the argument must be enclosed in double quotes, though ([ -z "$variable" ]
) as otherwise there is no argument for -z
to test.
There are many other test operators; this is not an exhaustive list.
Shell equality operators (=, ==, -eq)
=
and ==
are for string comparisons-eq
is for numeric comparisons-eq
is in the same family as -lt
, -le
, -gt
, -ge
, and -ne
==
is specific to bash (not present in sh (Bourne shell), ...). Using POSIX =
is preferred for compatibility. In bash the two are equivalent, and in sh =
is the only one that will work.
$ a=foo
$ [ "$a" = foo ]; echo "$?" # POSIX sh
0
$ [ "$a" == foo ]; echo "$?" # bash-specific
0
$ [ "$a" -eq foo ]; echo "$?" # wrong
-bash: [: foo: integer expression expected
2
(Note: make sure to quote the variable expansions. Do not leave out the double-quotes above.)
If you're writing a #!/bin/bash
script then I recommend using [[
instead. The double square-brackets [[...]]
form has more features, a more natural syntax, and fewer gotchas that will trip you up. For example, double quotes are no longer required around $a
:
$ [[ $a == foo ]]; echo "$?" # bash-specific
0
See also:
- What's the difference between [ and [[ in Bash?
Shell Script: What's the difference between using test and if statement?
[
is usually an other name for the command test
, just expecting a ]
as last argument. You could actually write the two commands:
test -e /usr/local/nagios && echo yes
and
if test -e /usr/local/nagios; then echo yes; fi
test
and [
are juste shell commands performing checks and returning 0
or 1
, that's why you can use it like this [ -e file ] && echo exists
(or test -e file && echo exists
).
When writing a script a few line long, I usually find the use of if
clearer and more explicit.
As of your other question, relying on hard coded paths is usually a bad idea (you can install from source to another directory and yum
may install nagios somewhere else in the future or depending on some configuration). I suggest you try querying yum
to see if it has nagios installed (with yum info nagios
or yum list installed | grep nagios
for example). Then, if nagios wasn't installed with yum, but the binary is in your PATH
, you can try which nagios
which will give you the full path of the nagios
binary.
What's the difference between [ and [[ in Bash?
[[
is bash's improvement to the [
command. It has several enhancements that make it a better choice if you write scripts that target bash. My favorites are:
It is a syntactical feature of the shell, so it has some special behavior that
[
doesn't have. You no longer have to quote variables like mad because[[
handles empty strings and strings with whitespace more intuitively. For example, with[
you have to writeif [ -f "$file" ]
to correctly handle empty strings or file names with spaces in them. With
[[
the quotes are unnecessary:if [[ -f $file ]]
Because it is a syntactical feature, it lets you use
&&
and||
operators for boolean tests and<
and>
for string comparisons.[
cannot do this because it is a regular command and&&
,||
,<
, and>
are not passed to regular commands as command-line arguments.It has a wonderful
=~
operator for doing regular expression matches. With[
you might writeif [ "$answer" = y -o "$answer" = yes ]
With
[[
you can write this asif [[ $answer =~ ^y(es)?$ ]]
It even lets you access the captured groups which it stores in
BASH_REMATCH
. For instance,${BASH_REMATCH[1]}
would be "es" if you typed a full "yes" above.You get pattern matching aka globbing for free. Maybe you're less strict about how to type yes. Maybe you're okay if the user types y-anything. Got you covered:
if [[ $ANSWER = y* ]]
Keep in mind that it is a bash extension, so if you are writing sh-compatible scripts then you need to stick with [
. Make sure you have the #!/bin/bash
shebang line for your script if you use double brackets.
See also
- Bash FAQ - "What is the difference between test, [ and [[ ?"
- Bash Practices - Bash Tests
- Server Fault - What is the difference between double and single brackets in bash?
Why does the bash test -n command give the wrong result for the $@ (dollar at) positional parameter while ! test -z works?
While transforming this confusing observed behaviour into a test script and question that might be suitable for StackOVerflow, several things happened which led to the answer to this 'why' question.
The TL;DR answer is in section 5 below; I describe the entire process of discovery because I (re)learned quite a few things along the way and I think others will follow the same, if not a very similar, path of discovery. Google is not always your friend!
1. total Google Fu fail
First, 'google' didn't deliver anything when you search for bash test $@ odd result
or similar queries as it strips out the all-important $@
. Rephrasing $@
as dollar at
didn't spit out anything useful either, which got me rather worried. I even started to suspect the bash I was running (mSysGit bash on Windows).
Thanks to this debacle I went and looked up what the official name (jargon) for this
$@
might be (It's maybe 2 decades ago that I last read a bash manual; I've been dabbling in it ever since and this is the moment where I receive punishment for my laziness in not studying up on a language that I write (small bits of) code in.)
2. The name is: 'positional parameters'. $@
(and $*
)
Another round of searching led to http://www.tldp.org/LDP/abs/abs-guide.pdf which told me that $@
(and $*
- oh, right, had forgotten all about you!) are 'positional parameters'.
3. what is the exact behaviour of test
?
During this search frenzy I also found http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules which explains how test
behaves when the number of arguments is maybe not what you'ld expect.
But then I did have quotes around that $@
, didn't I?
So it must be a single argument for test
then, even when the argument list is empty!? ($# = 0
)
(Wrong! It isn't!)
The test
expression evaluation rule set
Copying (with minimal edit to match it to this question) the very important section from the link above here -- this is what made my brain click a get a hunch, then a full understanding:
http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules:
Number of Arguments Rules
The
test
builtin, especially hidden under its[
name, may seem simple but is in fact causing a lot of trouble sometimes. One of the difficulties is that the behaviour oftest
not only depends on its arguments but also on the number of its arguments.Here are the rules taken from the manual (Note: This is for the command
test
, for[
the number of arguments is calculated without the final]
, for example[ ]
follows the "zero arguments" rule):
0 arguments
The expression is
false
.1 argument
The expression is
true
if, and only if, the argument is not null.2 arguments
If the first argument is
!
(exclamation mark), the expression istrue
if, and only if, the second argument is null.If the first argument is one of the unary conditional operators listed above under the syntax rules (e.g.
-n
and-z
), the expression istrue
if the unary test istrue
.If the first argument is not a valid unary conditional operator, the expression is
false
.3 arguments
If the second argument is one of the binary conditional operators listed above under the syntax rules, the result of the expression is the result of the binary test using the first and third arguments as operands.
If the first argument is
!
, the value is the negation of the two-argument test using the second and third arguments.If the first argument is exactly
(
and the third argument is exactly)
, the result is the one-argument test of the second argument. Otherwise, the expression isfalse
. The-a
and-o
operators are considered binary operators in this case (Attention: This means the operator-a
is not a file operator in this case!)4 arguments
If the first argument is
!
, the result is the negation of the three-argument expression composed of the remaining arguments. Otherwise, the expression is parsed and evaluated according to precedence using the rules listed above.5 or more arguments
The expression is parsed and evaluated according to precedence using the rules listed above.
These rules may seem complex, but it's not so bad in practice. Knowing them might help you to explain some of the "unexplicable" behaviours you might encounter:
(note: the next section is paraphrasing the original to match this question!)
function test {
if [ -n "$@" ] ; then echo "argument list is not empty"; fi
}
test
This code prints "argument list is not empty", even though
-n something
is supposed to befalse
ifsomething
is an empty string""
- why?Here,
"$@"
expands to an empty argument list, i.e."$@"
results in actually nothing (Bash removes it from the command's argument list!). So the test is in fact[ -n ]
and falls into the "one argument" rule, the only argument is "-n
" which is not null and so the test returnstrue
. Hence the usual solution, which is to quote the parameter expansion, e.g.[ -n "$var" ]
so that the test has always 2 arguments, even if the second one is the null string, does not work for"$@"
.These rules also explain why, for instance,
-a
and-o
can have several meanings.
4. One more test run...
The description in http://www.tldp.org/LDP/abs/abs-guide.pdf and the non-obvious bits of behaviour of test
as described in http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules drove me to run a fourth test with the test script specified in the question:
$ ./bash_weirdness.sh x y z
->
args: 'x y z'
--- empty ---
1.A2.EMPTY - empty?
1.B1.NOT.TEST - empty?
1.B3.NOT.NE - empty?
--- space ---
2.A1.TEST - not empty?
2.A3.NE - not empty?
2.B2.NOT.EMPTY - not empty?
--- $@ ---
util/bash_weirdness.sh: line 25: test: y: binary operator expected
util/bash_weirdness.sh: line 26: test: too many arguments
util/bash_weirdness.sh: line 27: test: too many arguments
util/bash_weirdness.sh: line 28: test: y: binary operator expected
3.B1.NOT.TEST - empty?
util/bash_weirdness.sh: line 29: test: too many arguments
3.B2.NOT.EMPTY - not empty?
util/bash_weirdness.sh: line 30: test: too many arguments
3.B3.NOT.NE - empty?
--- $* ---
4.A1.TEST - not empty?
4.A3.NE - not empty?
4.B2.NOT.EMPTY - not empty?
Now there's a hint!
5. Answering the 'why?' question
Now I know.
$@
, even when quoted as "$@"
, 'somehow' is transformed to the exact number of arguments available in $@
, which for an empty argument list ($# = 0
) means that "$@"
represents exactly nothing: test -n "$@"
therefor 'expands' to test -n
which,
following the rules described in http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules, is almost identical to the example described there and follows the same '1 argument rule': the -n
in test -n
is therefor not the test option: return true if string is non-empty
but rather that 'one argument', not null and consequently test -n "$@" --> test -n --> test "-n" --> TRUE
which produces the unexpected erroneous test result shown in the third test run in the question.
The 'somehow' is due to what bash must do internally to guarantee that the number of parameters in "$@"
always equals the number of rounds in loop statements such as for f in "$@" ; do ... done
and always matches the number of parameters $#
.
If the processing of "$@"
for an empty parameter list would produce an empty string ""
rather than nothing at all, then such loop statements would execute one(1) round instead of the anticipated zero(0) rounds, which would be extremely counter-intuitive.
Once you've realized this, the description in http://www.tldp.org/LDP/abs/abs-guide.pdf for $@
versus $*
is obvious.
6. Deja Vu. Now that I know, SO suddenly delivers comparable questions (and answers)
This felt exactly like the old days when I was reading UNIX man pages after having worked with VMS: those 'man pages' were very concise and were more times than not only useful to me once I had already obtained the knowledge through other channels.
[Edit] Retrospection Note:
I say "wouldn't have helped me" for many SO questions listed below only because at the time when I was writing this answer I reflected and wondered if my question really was a duplicate question after all. These other questions, and the answers provided there, are all very valuable in their own right.
The "wouldn't have helped me" phrase is there so you may understand that my brain needed something different to resolve my confusion and loss of trust in my machines. It is saying "I needed exactly this rather than one of those" and thus shows my internal argument whether this question of mine is a duplicate or not.
My own conclusion now: one question technically is almost the same (apart from the
!
in! test -z
), while its answers came very close to what I needed, but, to me at least, this isn't a duplicate answer as it pays much more attention to the depth of the details of the why oftest
and$@
interaction. The precise question context required for this answer to be suitable makes this, at least to me, in retrospect, not a duplicate question. Though it comes close to being one.
Here they are:
A case in which both test -n and test -z are true
my Google Fu failed me; this one would very probably have answered my question straight away.
It lacks one important bit of detail which is the bit that made my brain fire up and initiate understanding: http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules
Difference between $@ and $* in bash scripting
Same subject; wouldn't have helped because it doesn't address the 'empty argument list' explicitly.
The duplicate flag leads to:
Parsing/passing command line arguments to a bash script - what is the difference between "$@" and "$*"?
Also wouldn't have worked for me. Unless perhaps if I'ld run the scripts listed in the answer there. The crux is that
"$@"
eats the quotes and produces exactly nothing when$# = 0
. Which is not obvious from"$1" ...
.What is the difference between "$@" and "$*" in Bash?
Wouldn't have helped me; the more elaborate description of
$@
given here doesn't mention the effect you get when you have zero(0) parameters ($# = 0
); all consequences of the phase "starting with one" are clear only when you already know this peculiarity.Idiomatic way to test if no positional params are given?
Which would have circumvented my problem due to too little active knowledge of the
bash
language:$#
didn't belong to my 'active vocabulary` until about half an hour ago.
Related Topics
Iterate Over a List of Files With Spaces
Defining a Variable With or Without Export
How to Symlink a File in Linux
Use Expect in a Bash Script to Provide a Password to an Ssh Command
Using Awk to Print All Columns from the Nth to the Last
Static Link of Shared Library Function in Gcc
Better Way to Rename Files Based on Multiple Patterns
Redirect All Output to File in Bash
How to Cat ≪≪Eof ≫≫ a File Containing Code
How to Pass the Password to Su/Sudo/Ssh Without Overriding the Tty
What Does "&" At the End of a Linux Command Mean
How to Merge Two Files Using Awk
Using Openssl to Get the Certificate from a Server
How to Start Solr Automatically
Bash Alias Command With Both Single and Double Quotes