Как я могу отрицать возвращаемое значение процесса?

101

Я ищу простой, но кроссплатформенный процесс отрицания, который отрицает значение, возвращаемое процессом. Он должен отображать 0 в какое-то значение! = 0 и любое значение! = 0 в 0, то есть следующая команда должна возвращать «да, несуществующий путь не существует»:

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

! - оператор отличный, но, к сожалению, не зависит от оболочки.

секр
источник
Из любопытства, вы имели в виду конкретную систему, которая по умолчанию не включает bash? Я предполагаю, что под «кроссплатформенностью» вы имели в виду только * nix-based, поскольку вы приняли ответ, который будет работать только в * nix-системе.
Парфянский выстрел

Ответы:

110

Раньше ответ был представлен тем, что теперь первый раздел и последний раздел.

POSIX Shell включает !оператор

Пробираясь по спецификации оболочки для решения других проблем, я недавно (сентябрь 2015 г.) заметил, что оболочка POSIX поддерживает !оператор. Например, оно указано как зарезервированное слово и может появляться в начале конвейера, где простая команда является частным случаем «конвейера». Следовательно, его можно использовать в ifоператорах и / whileили untilциклах - в 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команда

Символ '!' Оператор, встроенный в оболочку или предоставляемый операционными системами, доступен не везде. Однако написать его не так уж и сложно - приведенный ниже код восходит как минимум к 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
3
Переменные среды Bash должны следовать за !оператором. У меня это сработало:! MY_ENV=value my_command
Даниэль Бёмер
1
Отрицание работает для всего канала, поэтому вы не можете этого сделать, ldd foo.exe | ! grep badlibно можете сделать, ! ldd foo.exe | grep badlibесли хотите, чтобы статус выхода был равен 0, если badlib не найден в foo.exe. Семантически вы хотите инвертировать grepстатус, но инвертирование всей трубы дает тот же результат.
Марк Лаката
@MarkLakata: Вы правы: см. Синтаксис оболочки POSIX и связанные разделы (перейдите в POSIX, чтобы получить лучший внешний вид). Тем не менее, целесообразно использовать ldd foo.exe | { ! grep badlib; }(хотя это неудобство, особенно точка с запятой, вы можете использовать полную подоболочку с (и )без точки с запятой). 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, по крайней мере, я не знаю как.
Аттила О.
1
Вы всегда можете поместить что-нибудь в сценарий оболочки; git bisect run !в этом случае не видит .
toolforger
10

Если каким-то образом случится так, что у вас нет Bash в качестве оболочки (например, скрипты git или тесты puppet exec), вы можете запустить:

echo '! ls notexisting' | bash

-> retcode: 0

echo '! ls /' | bash

-> retcode: 1

Крис Сушиньски
источник
1
Чтобы добавить сюда еще одну крошку, это необходимо для строк в разделе скриптов travis-ci.
kgraney 02
Это также было необходимо для конвейеров BitBucket.
KumZ
3
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

или

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."
Роберт Гэмбл
источник
Я скопировал, если из исходного вопроса, я думал, что это часть конвейера, иногда я немного медлителен;)
Роберт Гэмбл
Эти два оператора почти эквивалентны, НО оставшееся возвращаемое значение $?будет другим. Первый может уйти, $?=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 (13 марта 2019 г.) от SZEDER Gábor ( szeder) .
См. Commit 99e37c2 , commit 9f82b2a , commit 900721e (13 марта 2019 г.) Йоханнеса Шинделина ( dscho) .
(Объединено Junio ​​C Hamano - gitster- в коммите 579b75a , 25 апреля 2019 г.)

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является командой подоболочки, приложение должно гарантировать, что (оператор в начале command1отделяется от !одного или нескольких <blank>символов.
Поведение зарезервированного слова, !за которым сразу следует (оператор, не определено. "

В то время как наиболее распространенные оболочки все еще интерпретируют это ' !' как "отрицание кода выхода последней команды в конвейере", ' mksh/lksh' этого не делают и вместо этого интерпретируют его как шаблон отрицательного имени файла.
В результате они пытаются запустить команду, составленную из имен путей в текущем каталоге (он содержит единственный каталог с именем ' main'), что, конечно же, не проходит тест.

Мы могли бы исправить это, просто добавив пробел между ' !' и ' (', но вместо этого давайте исправим это, удалив ненужную подоболочку. В частности, Commit 74ec8cf

VonC
источник
1

Решение без!, Без подоболочки, без 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."
4irmann
источник