Previously, the answer was presented with what's now the first section as the last section.
POSIX Shell includes a !
operator
Poking around the shell specification for other issues, I recently (September 2015) noticed that the POSIX shell supports a !
operator. For example, it is listed as a reserved word and can appear at the start of a pipeline — where a simple command is a special case of 'pipeline'. It can, therefore, be used in if
statements and while
or until
loops too — in POSIX-compliant shells. Consequently, despite my reservations, it is probably more widely available than I realized back in 2008. A quick check of POSIX 2004 and SUS/POSIX 1997 shows that !
was present in both those versions.
Note that the !
operator must appear at the beginning of the pipeline and negates the status code of the entire pipeline (i.e. the last command). Here are some examples.
# Simple commands, pipes, and redirects work fine.
$ ! some-command succeed; echo $?
1
$ ! some-command fail | some-other-command fail; echo $?
0
$ ! some-command < succeed.txt; echo $?
1
# Environment variables also work, but must come after the !.
$ ! RESULT=fail some-command; echo $?
0
# A more complex example.
$ if ! some-command < input.txt | grep Success > /dev/null; then echo 'Failure!'; recover-command; mv input.txt input-failed.txt; fi
Failure!
$ ls *.txt
input-failed.txt
Portable answer — works with antique shells
In a Bourne (Korn, POSIX, Bash) script, I use:
if ...command and arguments...
then : it succeeded
else : it failed
fi
This is as portable as it gets. The 'command and arguments' can be a pipeline or other compound sequence of commands.
A not
command
The '!' operator, whether built-in to your shell or provided by the o/s, is not universally available. It isn't dreadfully hard to write, though - the code below dates back to at least 1991 (though I think I wrote a previous version even longer ago). I don't tend to use this in my scripts, though, because it is not reliably available.
/*
@(#)File: $RCSfile: not.c,v $
@(#)Version: $Revision: 4.2 $
@(#)Last changed: $Date: 2005/06/22 19:44:07 $
@(#)Purpose: Invert success/failure status of command
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1991,1997,2005
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"
#ifndef lint
static const char sccs[] = "@(#)$Id: not.c,v 4.2 2005/06/22 19:44:07 jleffler Exp $";
#endif
int main(int argc, char **argv)
{
int pid;
int corpse;
int status;
err_setarg0(argv[0]);
if (argc <= 1)
{
/* Nothing to execute. Nothing executed successfully. */
/* Inverted exit condition is non-zero */
exit(1);
}
if ((pid = fork()) < 0)
err_syserr("failed to fork\n");
if (pid == 0)
{
/* Child: execute command using PATH etc. */
execvp(argv[1], &argv[1]);
err_syserr("failed to execute command %s\n", argv[1]);
/* NOTREACHED */
}
/* Parent */
while ((corpse = wait(&status)) > 0)
{
if (corpse == pid)
{
/* Status contains exit status of child. */
/* If exit status of child is zero, it succeeded, and we should
exit with a non-zero status */
/* If exit status of child is non-zero, if failed and we should
exit with zero status */
exit(status == 0);
/* NOTREACHED */
}
}
/* Failed to receive notification of child's death -- assume it failed */
return (0);
}
This returns 'success', the opposite of failure, when it fails to execute the command. We can debate whether the 'do nothing successfully' option was correct; maybe it should report an error when it isn't asked to do anything. The code in '"stderr.h"
' provides simple error reporting facilities - I use it everywhere. Source code on request - see my profile page to contact me.
ls
is not a good tool for testing the existence of a path. If you want to test that a path exists, a perfectly good tool exists for that; it's conveniently calledtest
(often spelled[
).if ! test -e nonextantpath; then echo "does not exist" >&2; fi
– Coitus