Shell logical not

16,798

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.

Share:
16,798

Related videos on Youtube

teknoraver
Author by

teknoraver

Kernel hacker C programmer

Updated on September 18, 2022

Comments

  • teknoraver
    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
      Angel Todorov over 7 years
      The shell will have test/[/[[ builtin too. I'd say whatever makes your code easier to read
    • drHogan
      drHogan over 7 years
      The 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 for test. The most often used expressions have their own opposite expression.
  • gogoud
    gogoud over 7 years
    I wasn't aware that -o and -a were deprecated inside single square brackets or with test, my man test doesn't say that they are. Are there alternatives (still using single square brackets or test)?
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @gogoud, yes using -o and -a make for unreliable expressions, use [ ... ] && [ ... ]. See the POSIX spec for the test utility
  • mosvy
    mosvy almost 5 years
    There's yet another difference -- it's how they interact with set -e. Compare sh -ce '[ ! foo ]; echo yup vs sh -ce '! [ foo ]; echo yup
  • Stéphane Chazelas
    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.