Shell logical not
Solution 1
portability consideration.
The !
keyword is POSIX but not Bourne while !
has been supported by the [
/test
command from the start.
So [ ! ... ]
is more portable than ! [ ... ]
.
Otherwise, as long as you don't use the deprecated -o
and -a
binary operators, they should be equivalent (if we put aside the parsing bugs in some old test
/[
implementations).
Actually, in the Bourne shell, to do
if ! cmd1; then
cmd2
fi
You had to do:
if cmd1; then
:
else
cmd2
fi
(or use cmd1 || cmd2
, though that could result in a different exit status in the end).
interaction with set -o errexit
/ set -e
/ ERR
trap
Outside of the condition part of if
/fi
statements, as noted by @mosvy, another difference between [ ! ... ]
and ! [ ... ]
is that the latter will not cause the shell to exit when ! [ ... ]
returns false and the errexit
option is on.
!
applied to any pipeline cancels the effect of errexit
as the shell considers the exit status of the pipeline is being used as a condition. The same applies for failing-pipeline || cmd
or failing-pipeline && cmd
...
While with [ ! ... ]
, !
is just a regular argument passed to the [
command and in the end as far as the shell and errexit
is concerned, it's just a [
command returning with either a success or failure exit status.
$ sh -ec '[ ! a = a ]; echo here'; echo "$?"
1
$ sh -ec '! [ a = a ]; echo here'; echo "$?"
here
0
That doesn't apply when those commands are run as part of the condition section of if
/while
/until
statements, as in there, errexit
doesn't apply.
The same applies to the ERR
trap of Korn-like shells:
$ ksh -c 'trap "echo OUCH" ERR; ! [ a = a ]'
$ ksh -c 'trap "echo OUCH" ERR; [ ! a = a ]'
OUCH
In practice that likely won't make a difference as [
is almost always used as a condition, where errexit
doesn't apply (whether it's in if
/while
/until
statements or followed by &&
/||
).
Solution 2
[ … ]
is equal to test …
, so ! [ … ]
is equal to ! test …
. That means, you negate the result of the command test
. In this case, !
is a shell command.
From info bash
, e.g. in section «pipelines»:
If the reserved word `!' precedes the pipeline, the exit status is the logical negation of the exit status as described above.
On the other side, [ ! … ]
means test ! …
. That means, you negate an expression within test. See !
in man test
:
! EXPRESSION
EXPRESSION is false
So it can have a different meaning. If you have complex expressions, the negation may only apply to a part.
It is up to you, what you prefere.
Related videos on Youtube
Comments
-
teknoraver almost 2 years
what is preferred between
if ! [ ... ]; then
and
if [ ! ... ]; then
actually they do the same result, is there a preferred syntax? in the former syntax the evaluated not is the shell builtin, while in the latter the not is the
test
one, does it make any difference?-
Angel Todorov over 7 yearsThe shell will have
test
/[
/[[
builtin too. I'd say whatever makes your code easier to read -
drHogan over 7 yearsThe first one looks more familiar to me because you use it with other commands than
test
too. I don't remember many use cases where I've used!
as argument fortest
. The most often used expressions have their own opposite expression.
-
-
gogoud over 7 yearsI wasn't aware that
-o
and-a
were deprecated inside single square brackets or withtest
, myman test
doesn't say that they are. Are there alternatives (still using single square brackets ortest
)? -
Stéphane Chazelas over 7 years@gogoud, yes using
-o
and-a
make for unreliable expressions, use[ ... ] && [ ... ]
. See the POSIX spec for thetest
utility -
mosvy almost 5 yearsThere's yet another difference -- it's how they interact with
set -e
. Comparesh -ce '[ ! foo ]; echo yup
vssh -ce '! [ foo ]; echo yup
-
Stéphane Chazelas almost 5 years@mosvy, good point, though it doesn't apply in the condition part in an
if
statement like in the OP's question. I'll add a note about it unless you want to add your own answer.