ps-aux
ps-aux

Reputation: 12166

Why does [ -f ${x} ] evaluate to true for empty x in Bash?

Lets have standard file checking code in Bash:

f=$1

if [ ! -f "${f}" ];then
    echo "Does not exit"
fi

Why, when I remove weak quoting like this: if [ ! -f ${f} ] does this evaluate to true but if [ ! -f "" ] to false ?

Upvotes: 2

Views: 102

Answers (1)

chepner
chepner

Reputation: 531708

There are several rules you must understand to make sense of POSIX shell and the test command.

  1. Unquoted parameter expansions are subject to word-splitting; quoted parameter expansions are not.

    (Note: I say POSIX shell above because zsh is a prominent example of a shell that does not subject unquoted parameter expansions to word-splitting by default. bash does conform to the POSIX specification on this point.)

  2. The behavior of the test command is defined by the number of arguments it receives. Only after the argument count is determined can you start to interpret what the arguments mean.

  3. test can also be spelled [, in which case the final argument must be ], but that argument is otherwise ignored for the purposes of determining how many arguments are passed to [. That is, [ -f "$f" ] and test -f "$f" behave identically.

  4. If the first argument of the test command is !, then it is ignored for the purposes of counting and evaluating the remaining arguments, but the result of the evaluation is negated.


Now, to your question. [ ! -f $f ] is first subjected to parameter expansion. By point 1 above, $f when unquoted expands to nothing; after word-splitting occurs, there is no non-empty string left to form a word. This means the shell sees [ ! -f ] to be evaluated. This is recognized as the test command receiving two arguments, ! and -f. By point 4, this means that the one-argument expression -f is evaluated, which is done by testing if the string is empty or not. -f is not an empty string, so test would succeed, but the ! inverts it, so test ultimately fails.

If $f were quoted, then parameter expansion produces [ ! -f "" ]; there is an explicit empty string passed to test. After setting aside ! and ], we are left with a two-argument call to test. With two arguments, the first must be a recognized unary operator, and the second is treated as an argument to the first. Now, -f is an operator that tests if its argument names an existing regular file. The empty string is not a valid file name, so there can be no such regular file; the test would fail, with ! inverting that so that the call to test succeeds.

Upvotes: 4

Related Questions