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

7

153

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.

Heidyheifer answered 14/12, 2008 at 22:20 Comment(2)
Out of curiosity, did you have a specific system in mind which doesn't include bash by default? I assume by "cross-platform" you just meant *nix-based, since you accepted an answer that would only work on a *nix system.Penitent
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 called test (often spelled [). if ! test -e nonextantpath; then echo "does not exist" >&2; fiCoitus
R
184

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.

Rubbery answered 14/12, 2008 at 23:40 Comment(8)
+1 for The 'command and arguments' can be a pipeline or other compound sequence of commands. Other solutions do not work with pipelineScrimpy
Bash environment variables must follow the ! operator. This worked for me: ! MY_ENV=value my_commandPederson
The negation works on the whole pipe, so you can't do ldd foo.exe | ! grep badlib but you can do ! ldd foo.exe | grep badlib if you want the exit status to be 0 if badlib is not found in foo.exe. Semantically you want to invert the grep status, but inverting the entire pipe gives the same result.Privy
@MarkLakata: You're right: see the POSIX shell syntax and related sections (go to POSIX for the best look'n'feel). However, it is feasible to use ldd foo.exe | { ! grep badlib; } (though it is a nuisance, especially the semicolon; you could use a full sub-shell with ( and ) and without the semicolon). OTOH, the object is to invert the exit status of the pipeline, and that is the exit status of the last command (except in Bash, sometimes).Rubbery
@JonathanLeffler - yes, that works also. Just try putting extra parens into a cmake add_custom_command step, and you'll see why I avoided the extra parentheses. :)Privy
According to this answer the ! command has an undesirable interaction with set -e, i.e. it causes the command to succeed with a 0 or non-zero exit code, instead of succeeding with only a non-zero exit code. Is there a concise way to make it succeed only with a non-zero exit code?Knepper
To partially answer my own question, you can use a subshell, like ( ! my_command ) && true || false. That works, but it's less elegant and makes it somewhat less obvious what's going on.Knepper
Please note that there must be a space after !, as otherwise it seems to be interpreted as a reference to previous command. That is echo "hey" && ! echo "ho" ; echo $? does what we want, but echo "hey" && !echo "ho" ; echo $? will expand to something depending on previous commands, for example to echo "hey" && echo "hey" && ! echo "ho" ; echo $? "ho" ; echo $? :)Billboard
C
48

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

! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist"
Chaparro answered 14/12, 2008 at 22:25 Comment(1)
More elaborate answer. E.g.: "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)."Beeswing
A
21

You could try:

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

or just:

! ls nonexistingpath
Alsacelorraine answered 14/12, 2008 at 22:26 Comment(3)
Unfortunately ! can't be used with git bisect run, or at least I don't know how.Screwed
You can always put stuff into a shell script; git bisect run doesn't see the ! in that case.Toxicosis
@AttilaO — I took the shell script approach and here's what I ended up doing: echo "! ls nonexistingpath" > /tmp/bisect.sh; chmod u+x /tmp/bisect.sh; git bisect run /tmp/bisect.shSheepfold
D
14

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

Distinctive answered 30/3, 2013 at 15:16 Comment(3)
To put another breadcrumb here, this is necessary for lines in the script section of travis-ci.Scenic
This was also necessary for BitBucket pipelines.Despite
What is an "exec test"?Beeswing
E
7

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."
Et answered 6/11, 2020 at 1:58 Comment(2)
Nice, it looks simple and portable. "test $? -gt 0" Note that (on some systems) the return code might be negative. Thus, I'd suggest using this instead: test $? -ne 0.Engineering
What is the idea? Using test to negate/invert the result code?Beeswing
V
5
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

or

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."
Vernverna answered 14/12, 2008 at 22:27 Comment(2)
I copied if from the original question, I thought it was part of a pipeline, I'm a little slow sometimes ;)Vernverna
These two statements are almost equivalent, BUT the return value left in $? will be different. The first one might leave $?=1 if the nonexistingpath does actual exist. The second one always leaves $?=0. If you are using this in a Makefile and need to rely on the return value, you have to be careful.Privy
J
3

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

Janayjanaya answered 26/4, 2019 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.