Як я можу заперечити повернене значення процесу?


101

Я шукаю простий, але міжплатформовий процес заперечення, який заперечує значення, яке повертає процес. Він повинен відобразити 0 до деякого значення! = 0 та будь-якого значення! = 0 до 0, тобто наступна команда повинна повернути "так, неіснуючий шлях не існує":

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

The! - оператор чудовий, але, на жаль, не незалежний від оболонки.


З цікавості ви мали на увазі конкретну систему, яка за замовчуванням не включає bash? Я припускаю, що під "крос-платформою" ви просто мали на увазі * nix, оскільки ви прийняли відповідь, яка працювала б лише у системі * 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команда

"!" оператор, вбудований у вашу оболонку або наданий 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

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 або ненульова виходу, а змінивши тільки з кодом ненульовим. Чи є стислий спосіб досягти успіху лише за допомогою ненульового коду виходу?
Елліотт Слатер,

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-скрипти або лялькові exec-тести), ви можете запустити:

echo '! ls notexisting' | bash

-> повторний код: 0

echo '! ls /' | bash

-> повторний код: 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 (Q2 2019) ілюструє таку кращу форму за допомогою:

Зафіксуйте 74ec8cf , зафіксуйте 3fae7ad , зафіксуйте 0e67c32 , зафіксуйте 07353d9 , зафіксуйте 3bc2702 , зафіксуйте 8c3b9f7 , зафіксуйте 80a539a , зафіксуйте c5c39f4 (13 березня 2019 р.) Від SZEDER Gábor ( szeder) .
Див. Коміт 99e37c2 , коміт 9f82b2a , коміт 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'), що, звичайно, не проходить тест.

Ми могли б це виправити, просто додавши пробіл між ' !' та ' (', але замість цього давайте виправимо це, видаливши непотрібну підшелудку. Зокрема, комітуйте 74ec8cf


1

Рішення без!, Без підшерти, без якщо і повинно працювати принаймні в 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."
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.