shellcross-platformnegate

How can I negate the return-value of a process?


I'm looking for a simple, but cross-platform negate-process that negates the value a process returns. It should map 0 to some value != 0 and any value != 0 to 0, i.e. the following command should return "yes, nonexistingpath doesn't exist":

ls nonexistingpath | negate && echo "yes, nonexistingpath doesn't exist."

The ! - operator is great but unfortunately not shell-independent.


Solution

  • 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.