What do the -n and -a options do in a bash if statement? [duplicate]

Solution 1:

-a Links two expressions together in an "and" or "&&" expression. This option is deprecated.

-n Checks if the length of a string is nonzero.

You could translate the test expression into the following pseudocode:

if ( ($1 has nonzero length) and
     ($2 has nonzero length) and
     ($3 has nonzero length) )

There are no checks in that expression for whether the file exists or doesn't exist, only whether the arguments have been supplied to the script.

The arguments -a and -n can be found in the manpage for test

man test

The operator [ ... ] is often used as shorthand for test ... and likely has identical functionality on your system.

Solution 2:

Nitpicking

The switches -a and -n are not strictly part of a bash if statement in that the if command does not process these switches.

What are primaries?

I call them "switches", but the bash documentation that you linked to refers to the same thing as "primaries" (probably because this is a common term used when discussing parts of a boolean expression).

Background and docs

In sh scripts if is a command that takes a command as its argument, executes it and tests its return code. If the return code is 0 the block of code following then is executed up until the closing fi or (if supplied) the following else. If the return code was not 0 and an else statement was supplied then the block of code following else is executed up until the closing fi.

You can see this effect by passing if the command true or the command false, which are simple commands that do nothing and return 0 and non-0 respectively.

if true ; then echo true was true ; else echo true was false ; fi
if false ; then echo false was true ; else echo false was false ; fi

In the sample code you provided the command that you're passing to if is [, which is also sometimes known as test. It is this command which takes the switches you're asking about. In bash the test command will be a built-in command; try type [ to learn its type. For built-in commands help will show usage, so also run help [ to see documentation. Your system probably also has a /bin/[ and a /bin/test and if you man test you can see the manuals for those. Although the behavior of the built-in test may not be identical to the behavior documented in the man pages, which is likely more verbose than the simple description you'll get from help [, it will probably describe the behavior of the built-in [ command fairly accurately.

The behavior of -a and -n

Knowing that the command you're running is test we can consult help test or man test and read its usage. This will show that-n tests the following argument and evaluates to true if it is not an empty string.

In the documentation of test you will also see a the switch -e. This switch tests the following argument and evaluates to true if that argument is a file or directory that exists. More useful still is the -f switch which evaluates to true if the following argument exists and is a regular file (as opposed to a directory or a block device, or whatever).

The source of your confusion is probably that there can be two forms of -a: Unary and binary. When -a is used in a unary context, that is with one following argument but no preceding arguments, it treats its argument as a file and tests for its existence, just like the -e switch. However, when -a is used in a binary context, that is with one argument before it and one argument after it, it treats its arguments as other conditions and acts as a boolean AND operator.

In the interests of portability it is important to note that unary -a is a non-standard extension which won't be found in POSIX. It is available in bash and ksh, however, so usage is probably widespread.

Example

cd /tmp
if [ -a test-file ] ; then
    echo 1: test-file exists
else
    echo 1: test-file missing
fi

touch test-file

if [ -a test-file ] ; then
    echo 2: test-file exists
else
    echo 2: test-file missing
fi

var=somerthing
if [ -n "$var" -a -a test-file ] ; then
    echo variable var is not empty and test-file exists
fi
rm -f test-file