如何取消流程的返回值?


103

我正在寻找一个简单但跨平台的否定-process,它否定一个进程返回的值。它应该将0映射到某个值!= 0,将任何值!= 0映射到0,即以下命令应返回“是,不存在的路径不存在”:

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

!-运算符很棒,但不幸的是不是独立于shell的。


出于好奇,您是否想到了一个特定的系统,该系统默认不包含bash?我认为“跨平台”只是基于* nix的意思,因为您接受了仅在* nix系统上有效的答案。
Parthian Shot 2014年

Answers:


115

以前,答案是第一部分,最后一部分。

POSIX Shell包含一个!运算符

围绕外壳规范中的其他问题,我最近(2015年9月)注意到POSIX外壳支持一个!运算符。例如,它被列为保留字,并且可以出现在管道的开头,其中简单的命令是“管道”的特例。因此,它也可以在if语句和/whileuntil循环中使用-在POSIX兼容的外壳中。因此,尽管我有所保留,但它的可用范围可能比我在2008年意识到的范围要广。对POSIX 2004和SUS / POSIX 1997的快速检查表明,!这两个版本中都存在。

请注意,!操作员必须出现在管道的开头,并且否定整个管道的状态代码(即最后一条命令)。这里有些例子。

# 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

便携式答案-适用于仿古贝壳

在Bourne(Korn,POSIX,Bash)脚本中,我使用:

if ...command and arguments...
then : it succeeded
else : it failed
fi

这是随身携带的。“命令和参数”可以是管道或其他复合命令序列。

一个not命令

'!' 不管是内置在您的外壳中还是由o / s提供的操作符,并不是通用的。不过,编写该代码并不可怕-下面的代码至少可以追溯到1991年(尽管我认为我是在更早的时候才编写以前的版本的)。但是,我不太倾向于在我的脚本中使用它,因为它不可靠。

/*
@(#)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);
}

如果执行命令失败,则返回“成功”,与失败相反。我们可以辩论“不做任何成功”选项是否正确;也许当它不被要求做任何事情时,它应该报告一个错误。“ "stderr.h"”中的代码提供了简单的错误报告功能-我到处都使用它。根据要求提供源代码-请参阅我的个人资料页面与我联系。


“命令和参数”的+1可以是管道或其他复合命令序列。其他解决方案不适用于管道
HVNSweeting 2014年

3
Bash环境变量必须遵循!运算符。这对我! MY_ENV=value my_command
有用

1
否定作用于整个管,所以你不能这样做ldd foo.exe | ! grep badlib,但你可以做! ldd foo.exe | grep badlib,如果你想退出状态是0,如果badlib是不是在foo.exe的发现。在语义上,您想反转grep状态,但是反转整个管道会得到相同的结果。
Mark Lakata '16

@MarkLakata:您是对的:请参阅POSIX Shell语法和相关部分(请转到POSIX以获得最佳外观)。但是,它是使用可行的ldd foo.exe | { ! grep badlib; }(虽然它是一个麻烦事,尤其是分号,你可以使用一个完整的子shell有()和没有分号)。OTOH,目的是反转管道的退出状态,即最后一条命令的退出状态(有时在Bash中除外)。
乔纳森·勒夫勒

3
根据此答案,!命令与交互作用不佳set -e,即,它使命令以0或非零退出代码成功,而不是仅以非零退出代码成功。是否有一种简洁的方法可以使它仅以非零退出码成功?
Elliott Slaughter

40

在Bash中,使用!命令前的运算符。例如:

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

17

您可以尝试:

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

要不就:

! ls nonexistingpath

5
不幸的是!不能与一起使用git bisect run,或者至少我不知道如何使用。
Attila O. 2012年

1
您总是可以将内容放入shell脚本中;git bisect run!在这种情况下看不到。
toolforger

10

如果由于某种原因您没有使用Bash作为外壳程序(例如git脚本或puppet exec测试),则可以运行:

echo '! ls notexisting' | bash

->重新编码:0

echo '! ls /' | bash

->重新编码:1


1
要在此处放置另一个面包屑,这对于travis-ci脚本部分中的行是必需的。
kgraney

这对于BitBucket管道也是必需的。
KumZ

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

要么

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

我是从最初的问题中复制出来的,是否以为它是管道的一部分,有时我有点慢;)
Robert Gamble

这两个语句几乎相等,但是返回的值$?将有所不同。$?=1如果nonexistingpath确实存在,第一个可能会离开。第二个总是离开$?=0。如果在Makefile中使用此函数,并且需要依赖返回值,则必须小心。
马克·拉卡塔

1

注意:有时您会看到!(command || other command)
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."就够了。
无需子外壳。

Git 2.22(2019年第二季度)通过以下方式说明了更好的形式:

提交74ec8cf提交3fae7ad提交0e67c32提交07353d9提交3bc2702提交8c3b9f7提交80a539a提交c5c39f4(2019年3月13日)通过SZEDERGábor(szeder
请参阅Johannes Schindelin()的提交99e37c2提交9f82b2a提交900721e(2019年3月13日(通过合并JUNIOÇ滨野- -提交579b75a,2019年4月25日)dscho
gitster

t9811-git-p4-label-import:修复管道求反

在' t9811-git-p4-label-import.sh'中,测试' tag that cannot be exported'运行:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

检查给定的字符串不是由' p4 labels'打印。
这是有问题的,因为根据POSIX

“如果流水线以保留字开头!并且command1是子shell命令,则应用程序应确保(操作符的开头与字符之间用一个或多个字符command1分隔。未指定保留字的行为,紧随操作符之后!<blank>
!(

尽管大多数常见的shell仍将此解释!为“'使管道中最后一个命令的退出代码mksh/lksh无效”,但不要将其解释为负文件名模式。
结果,他们尝试运行由当前目录中的路径名组成的命令(该路径名包含一个名为' main'的目录),该命令当然会通过测试。

我们可以通过在' !'和' ('之间添加一个空格来简单地对其进行修复,而让我们通过删除不必要的subshel​​l来对其进行修复。特别是,提交74ec8cf


1

没有!,没有subshel​​l,没有if的解决方案,至少应该在bash中工作:

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

# alternatively without error message and handling of any error code > 0
ls nonexistingpath 2>/dev/null; test $? -gt 0 && echo "yes, nonexistingpath doesn't exist."
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.