'Repeated negation (!) operators in bash do not negate each other
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.
Solution 1:[1]
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;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Community |