Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repeated negation (!) operators in bash extended test syntax do not negate each other?

Tags:

bash

negation

So this is more an oddity I've come up against than something I really want to use. But I found something I didn't understand with the bash extended test syntax.

Check this out (included my shell version in case it matters):

34>$SHELL --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
35>[ ! -d /tmp ] && echo Hi
36>[ ! ! -d /tmp ] && echo Hi
Hi
37>[[ ! -d /tmp ]] && echo Hi
38>[[ ! ! -d /tmp ]] && echo Hi
39>

OK, so lines 35 and 36, using the normal test, operate as I expect. The single bang doesn't print a line (because /tmp exists), and the double bang does.

Line 37, using extended bash syntax, also doesn't print anything, as I would expect. But line 38 doesn't either! This is surprising to me; it indicates that the directory doesn't exist, but also doesn't not exist?

Searching for information on this has been frustrating. Am I missing something here? An unmentioned syntax error? I just want to understand why this happens.

like image 595
Swandog Avatar asked Apr 17 '18 17:04

Swandog


People also ask

What does != Mean in bash?

The origin of != is the C family of programming languages, in which the exclamation point generally means "not". In bash, a ! at the start of a command will invert the exit status of the command, turning nonzero values to zero and zeroes to one.

How do you negate a test in bash?

To negate any condition, use the ! operator, for example: if ! <test-command>; then <command-on-failure>; fi . Note that the space between the ! and the following command is important, otherwise, it would perform the bash history expansion and most-likely will return a bash error event not found .

What does [- Z $1 mean in bash?

$1 means an input argument and -z means non-defined or empty. You're testing whether an input argument to the script was defined when running the script. Follow this answer to receive notifications.

How do you negate a condition in shell script?

NegationWhen we use the not operator outside the [[, then it will execute the expression(s) inside [[ and negate the result. If the value of num equals 0, the expression returns true. But it's negated since we have used the not operator outside the double square brackets.


1 Answers

Due to the use of a flag in the BASH code which is not toggled in this particular case, only the first instance of ! matters, unless brackets are separating them.

To find this out, first I took a look at the Bash(1) Man Page:

Reserved Words

Reserved words are words that have a special meaning to the shell. [...] words are recognized as reserved when unquoted and either the first word of a simple command (see SHELL GRAMMAR below) or the third word of a case or for command

Which hints to the usage of the ! reserved word.

However, this doesn't really explain it very well. So I took a look at the source code (version 4.4.18) for Bash.

It looks like command.h contains a flag which is set for a command, when it detects the ! symbol:

#define CMD_INVERT_RETURN  0x04 /* Invert the exit value. */

This is used several times in the execute_cmd.c file:

invert = (command->flags & CMD_INVERT_RETURN) != 0;

But this file seems to just check for the presence of the flag. I believe the parsing of it is done in another file.

On line 4507 of parse.y, we can see that it seems to just be set, not toggled. This means that it doesn't matter how many occurrences of BANG (!) there is, it'll only set the flag once.

else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0'))) {
  if (tok == WORD)
dispose_word (yylval.word); /* not needed */
  term = cond_term ();
  if (term)
term->flags |= CMD_INVERT_RETURN;
}

I find this behaviour strange, since later in the code, there is support for toggling this value, on line 1201 of parse.y, which relates to pipelines (formatted for readability)

pipeline_command: pipeline
{ $$ = $1; }            
    | BANG pipeline_command {
        if ($2)
            $2->flags ^= CMD_INVERT_RETURN; /* toggle */
        $$ = $2;
    }
like image 182
Addison Avatar answered Sep 28 '22 04:09

Addison