&&' Vs. '&' With the 'Test' Command in Bash

'&&' 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:

  1. 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 write

    if [ -f "$file" ]

    to correctly handle empty strings or file names with spaces in them. With [[ the quotes are unnecessary:

    if [[ -f $file ]]
  2. 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.

  3. It has a wonderful =~ operator for doing regular expression matches. With [ you might write

    if [ "$answer" = y -o "$answer" = yes ]

    With [[ you can write this as

    if [[ $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.

  4. 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 of test 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 is true 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 is true if the unary test is true.

    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 is false. 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 be false if something 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 returns true. 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 of test 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



Leave a reply



Submit