¿Cómo puedo negar el valor de retorno de un proceso?


101

Estoy buscando un proceso de negación simple pero multiplataforma que niegue el valor que devuelve un proceso. ¡Debe asignar 0 a algún valor! = 0 y cualquier valor! = 0 a 0, es decir, el siguiente comando debe devolver "sí, la ruta no existente no existe":

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

Los ! - el operador es excelente, pero desafortunadamente no es independiente del shell.


Por curiosidad, ¿tenías en mente un sistema específico que no incluye bash por defecto? Supongo que por "multiplataforma" solo se refería a * basado en * nix, ya que aceptó una respuesta que solo funcionaría en un sistema * nix.
Parthian Shot

Respuestas:


110

Anteriormente, la respuesta se presentaba con lo que ahora es la primera sección como la última sección.

POSIX Shell incluye un !operador

Al hurgar en la especificación del shell por otros problemas, recientemente (septiembre de 2015) noté que el shell POSIX admite un !operador. Por ejemplo, aparece como una palabra reservada y puede aparecer al comienzo de una canalización , donde un comando simple es un caso especial de 'canalización'. Por lo tanto, también se puede usar en ifdeclaraciones whileo untilbucles, en shells compatibles con POSIX. En consecuencia, a pesar de mis reservas, probablemente esté más disponible de lo que me di cuenta en 2008. Una revisión rápida de POSIX 2004 y SUS / POSIX 1997 muestra que !estaba presente en ambas versiones.

Tenga en cuenta que el !operador debe aparecer al comienzo de la tubería y niega el código de estado de toda la tubería (es decir, el último comando). Aquí hay unos ejemplos.

# 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

Respuesta portátil: funciona con conchas antiguas

En un script de Bourne (Korn, POSIX, Bash), uso:

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

Esto es lo más portátil posible. El 'comando y argumentos' puede ser una canalización u otra secuencia compuesta de comandos.

Un notcomando

Los '!' El operador, ya sea integrado en su shell o proporcionado por el o / s, no está disponible universalmente. Sin embargo, no es terriblemente difícil de escribir: el código a continuación se remonta al menos a 1991 (aunque creo que escribí una versión anterior incluso hace más tiempo). Sin embargo, no suelo usar esto en mis scripts porque no está disponible de manera confiable.

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

Esto devuelve "éxito", lo opuesto al fracaso, cuando no se ejecuta el comando. Podemos debatir si la opción "no hacer nada con éxito" era correcta; tal vez debería informar un error cuando no se le pide que haga nada. El código en " "stderr.h"" proporciona funciones sencillas de notificación de errores; lo uso en todas partes. Código fuente a pedido - vea mi página de perfil para contactarme.


+1 para El 'comando y argumentos' puede ser una canalización u otra secuencia compuesta de comandos. Otras soluciones no funcionan con pipeline
HVNSweeting

3
Las variables de entorno Bash deben seguir al !operador. Esto funcionó para mí:! MY_ENV=value my_command
Daniel Böhmer

1
La negación funciona en toda la tubería, por lo que no puede hacerlo, ldd foo.exe | ! grep badlibpero puede hacerlo ! ldd foo.exe | grep badlibsi desea que el estado de salida sea 0 si no se encuentra badlib en foo.exe. Semánticamente, desea invertir el grepestado, pero al invertir toda la tubería se obtiene el mismo resultado.
Mark Lakata

@MarkLakata: Tienes razón: consulta la sintaxis del shell POSIX y las secciones relacionadas (ve a POSIX para obtener la mejor apariencia y sensación). Sin embargo, es factible utilizar ldd foo.exe | { ! grep badlib; }(aunque es una molestia, especialmente el punto y coma; usted podría utilizar un sub-shell completa con (y )sin el punto y coma). OTOH, el objeto es invertir el estado de salida de la tubería, y ese es el estado de salida del último comando (excepto en Bash, a veces).
Jonathan Leffler

3
De acuerdo con esta respuesta, el !comando tiene una interacción no deseada con set -e, es decir, hace que el comando tenga éxito con un código de salida 0 o distinto de cero, en lugar de tener éxito solo con un código de salida distinto de cero. ¿Existe una forma concisa de hacerlo exitoso solo con un código de salida distinto de cero?
Elliott Slaughter

40

En Bash, usa el! operador antes del comando. Por ejemplo:

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

17

Tu podrías intentar:

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

o solo:

! ls nonexistingpath

5
Desafortunadamente !, no se puede usar con git bisect run, o al menos no sé cómo.
Attila O.5 de

1
Siempre puedes poner cosas en un script de shell; git bisect run no ve el !en ese caso.
toolforger

10

Si de alguna manera sucede que no tiene Bash como su shell (por ejemplo: scripts git o pruebas ejecutivas de títeres), puede ejecutar:

echo '! ls notexisting' | bash

-> código de retiro: 0

echo '! ls /' | bash

-> código de retiro: 1


1
Para poner otra ruta de navegación aquí, esto es necesario para las líneas en la sección de script de travis-ci.
kgraney

Esto también era necesario para las canalizaciones de BitBucket.
KumZ

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

o

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

Copié si de la pregunta original pensé que era parte de una tubería, a veces soy un poco lento;)
Robert Gamble

Estas dos declaraciones son casi equivalentes, PERO el valor de retorno que queda $?será diferente. El primero podría irse $?=1si nonexistingpathrealmente existe. El segundo siempre se va $?=0. Si está usando esto en un Makefile y necesita confiar en el valor de retorno, debe tener cuidado.
Mark Lakata

1

Nota: a veces verá !(command || other command).
Aquí ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."es suficiente.
No se necesita un sub-caparazón.

Git 2.22 (Q2 2019) ilustra esa mejor forma con:

Confirma 74ec8cf , confirma 3fae7ad , confirma 0e67c32 , confirma 07353d9 , confirma 3bc2702 , confirma 8c3b9f7 , confirma 80a539a , confirma c5c39f4 (13 de marzo de 2019) por SZEDER Gábor ( szeder) .
Consulte la confirmación 99e37c2 , la confirmación 9f82b2a , la confirmación 900721e (13 de marzo de 2019) de Johannes Schindelin ( dscho) .
(Combinado con Junio ​​C Hamano - gitster- en el compromiso 579b75a , 25 de abril de 2019)

t9811-git-p4-label-import: arregla la negación de la tubería

En ' t9811-git-p4-label-import.sh', la prueba ' tag that cannot be exported' se ejecuta:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

para comprobar que la cadena dada no está impresa por " p4 labels".
Esto es problemático, porque según POSIX :

"Si la canalización comienza con la palabra reservada !y command1es un comando de subshell, la aplicación debe asegurarse de que el (operador al principio de command1esté separado del de !por uno o más <blank>caracteres.
El comportamiento de la palabra reservada !seguida inmediatamente por el (operador no se especifica. "

Mientras que los shells más comunes todavía interpretan esto ' !' como "niega el código de salida del último comando en la canalización", ' mksh/lksh' no lo interpretan como un patrón de nombre de archivo negativo.
Como resultado, intentan ejecutar un comando compuesto por los nombres de ruta en el directorio actual (contiene un solo directorio llamado ' main'), que, por supuesto, no pasa la prueba.

Podríamos arreglarlo simplemente agregando un espacio entre ' !' y ' (', pero en su lugar, vamos a arreglarlo eliminando la subcapa innecesaria. En particular, comprometa 74ec8cf


1

Solución sin!, Sin subshell, sin if, y debería funcionar al menos en 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."
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.