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

ShellCross PlatformNegate

Shell Problem Overview


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.

Shell Solutions


Solution 1 - Shell

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.

Solution 2 - Shell

In Bash, use the ! operator before the command. For instance:

! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist"

Solution 3 - Shell

You could try:

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

or just:

! ls nonexistingpath

Solution 4 - Shell

If somehow happens that you don't have Bash as your shell (for example: Git scripts, or Puppet exec tests) you can run:

echo '! ls notexisting' | bash

-> retcode: 0

echo '! ls /' | bash

-> retcode: 1

Solution 5 - Shell

Solution without !, without subshell, without if, and should work at least in Bash:

ls nonexistingpath; test $? -eq 2 && echo "yes, nonexistingpath doesn't exist."

# Alternatively without an error message and handling of any error code > 0
ls nonexistingpath 2>/dev/null; test $? -gt 0 && echo "yes, nonexistingpath doesn't exist."

Solution 6 - Shell

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

or

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

Solution 7 - Shell

Note: sometimes you will see !(command || other command).
Here ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist." is enough.
No need for a sub-shell.

Git 2.22 (Q2 2019) illustrates that better form with:

Commit 74ec8cf, commit 3fae7ad, commit 0e67c32, commit 07353d9, commit 3bc2702, commit 8c3b9f7, commit 80a539a, commit c5c39f4 (13 Mar 2019) by SZEDER Gábor (szeder).
See commit 99e37c2, commit 9f82b2a, commit 900721e (13 Mar 2019) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit 579b75a, 25 Apr 2019)

> ## t9811-git-p4-label-import: fix pipeline negation

> In 't9811-git-p4-label-import.sh', the test 'tag that cannot be exported' runs:

> !(p4 labels | grep GIT_TAG_ON_A_BRANCH)

> to check that the given string is not printed by 'p4 labels'.
This is problematic, because according to POSIX:

> > "If the pipeline begins with the reserved word ! and command1 is a subshell command, the application shall ensure that the ( operator at the beginning of command1 is separated from the ! by one or more <blank> characters.
The behavior of the reserved word ! immediately followed by the ( operator is unspecified."

> While most common shells still interpret this '!' as "negate the exit code of the last command in the pipeline", 'mksh/lksh' don't and interpret it as a negative file name pattern instead.
As a result, they attempt to run a command made up of the pathnames in the current directory (it contains a single directory called 'main'), which, of course, fails the test.

> We could fix it simply by adding a space between the '!' and '(', but instead let's fix it by removing the unnecessary subshell. In particular, Commit 74ec8cf

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsecrView Question on Stackoverflow
Solution 1 - ShellJonathan LefflerView Answer on Stackoverflow
Solution 2 - ShellJay ConrodView Answer on Stackoverflow
Solution 3 - ShellEduard WirchView Answer on Stackoverflow
Solution 4 - ShellChris SuszyńskiView Answer on Stackoverflow
Solution 5 - Shell4irmannView Answer on Stackoverflow
Solution 6 - ShellRobert GambleView Answer on Stackoverflow
Solution 7 - ShellVonCView Answer on Stackoverflow