bash if not multiple conditions without subshell?

You need to use { list;} instead of (list):

if ! { [ -f file1 ] && [ -f file2 ] && [ -f file3 ]; }; then
  : do something
fi

Both of them are Grouping Commands, but { list;} executes commands in current shell environment.

Note that, the ; in { list;} is needed to delimit the list from } reverse word, you can use other delimiter as well. The space (or other delimiter) after { is also required.


To portably negate a complex conditional in shell, you must either apply De Morgan's law and push the negation all the way down inside the [ calls...

if [ ! -f file1 ] || [ ! -f file2 ] || [ ! -f file3 ]
then
    # do stuff
fi

... or you must use then :; else ...

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ]
then :
else
  # do stuff
fi

if ! command is not portably available and neither is [[.

If you don't need total portability, don't write a shell script. You're actually more likely to find /usr/bin/perl on a randomly selected Unix than you are bash.


You could use entirely the test functionality to achieve what you want. From the man page of test:

 ! expression  True if expression is false.
 expression1 -a expression2
               True if both expression1 and expression2 are true.
 expression1 -o expression2
               True if either expression1 or expression2 are true.
 (expression)  True if expression is true.

So your condition could look like:

if [ -f file1 -a -f file2 -a -f file3 ] ; then
    # do stuff with the files
fi

For negating use escaped parentheses:

if [ ! \( -f file1 -a -f file2 -a -f file3 \) ] ; then
    echo "Error: You done goofed."
    exit 1
fi
# do stuff with the files