Cómo mantener el último estado de salida después de la prueba


8

¿Es posible mantener $?inalterado el último estado de salida del comando ( ) después de una prueba?

Por ejemplo, me gustaría hacer:

command -p sudo ...
[ $? -ne 1 ] && exit $?

El último exit $?debería devolver el estado de salida de sudo, pero en su lugar siempre regresa 0(el código de salida de la prueba).

¿Es posible hacer eso sin una variable temporal?

Otro ejemplo para aclarar más:

 spd-say "$@"
 [ $? -ne 127 ] && exit $?

En este caso, quiero salir solo si se encuentra el primer comando (código de salida! = 127). Y quiero salir con el spd-saycódigo de salida real (puede que no lo sea 0).

EDITAR: Olvidé mencionar que prefiero una solución de queja POSIX para una mejor portabilidad.

Utilizo esta construcción en scripts donde quiero proporcionar alternativas para el mismo comando. Por ejemplo, vea mi script crc32 .

El problema con las variables temporales es que podrían sombrear otras variables, y para evitar que debas usar nombres largos, lo que no es bueno para la legibilidad del código.


No, pero puede hacer lo if ! command -p sudo; then exit; fique tendría los mismos resultados para su ejemplo.
jordanm

ok, ¿qué pasa si quiero probar el código 127 en su lugar? (p. ej. [ $? -ne 127 ] && exit $?)
eadmaster

1
@jordanm: ¿En serio? Si el OP ha presentado el código / lógica que quiso, y si lo estoy leyendo correctamente, quiere que el script salga si el sudocomando tiene éxito (es decir, si sudosale con el estado 0). Pero, en su código, el script sigue ejecutándose (no sale) si sudotiene éxito
G-Man dice 'Reinstate Monica'

@ G-Man: estoy bastante seguro de que las condiciones del autor de la pregunta se basan realmente en el retorno commandy no sudoen absoluto. Eso es lo que significa que quiero salir solo si se encuentra el primer comando (código de salida! = 127) y es un retorno especificado paracommand cuando no se encuentra el comando que invoca. Supongo que el problema es que invocar sudocomo parte de la prueba permite sudoaplastar el retorno commanden primer lugar y así sesgar la prueba.
mikeserv

Respuestas:


3

$_trabajará en (al menos) interactiva dash, bash, zsh, ksh (aunque aparentemente no en una sentencia condicional conforme a lo solicitado) y mkshconchas. De ellos, que yo sepa, solo bashy zshtambién lo completará en un shell con script. No es un parámetro POSIX, pero es bastante portátil para cualquier shell moderno e interactivo.

Para una solución más portátil, puede hacer:

command -p sudo ...
eval '[ "$?" = 127 ] || exit '"$?"

Lo que básicamente le permite expandir el valor inicial $?a la cola del script antes de probar su valor en su cabeza.

Pero de todos modos, dado que parece estar probando si el comando sudose puede encontrar o no en la cadena de -p ruta portátil incorporada del shell command, creo que podría ir un poco más directamente. Además, para ser claros, command no probará la ubicación de ningún argumento para sudo, por lo que es solo sudo, y nada de lo que invoca, lo que es relevante para ese valor de retorno.

Y de todos modos, si eso es lo que estás tratando de hacer:

command -pv sudo >/dev/null || handle_it
command -p  sudo something or another

... funcionaría bien como prueba sin ninguna posibilidad de que los errores en las sudoejecuciones de comandos regresen de tal manera que puedan sesgar los resultados de su prueba.


9

Hay varias opciones para manejar el estado de salida de manera confiable sin sobrecarga, dependiendo de los requisitos reales.

Puede guardar el estado de salida utilizando una variable:

command -p sudo ...
rc=$?
[ "$rc" -ne 1 ] && echo "$rc"

Puede verificar directamente el éxito o el fracaso:

if  command -p sudo ...
then  echo success
else  echo failure
fi

O use una caseconstrucción para diferenciar el estado de salida:

command -p sudo ...
case $? in
(1) ... ;;
(127) ... ;;
(*) echo $? ;;
esac

con el caso especial que se hace en la pregunta:

command -p sudo ...
case $? in (1) :;; (*) echo $?;; esac

Todas esas opciones tienen la ventaja de que se ajustan al estándar POSIX.

(Nota: para la ilustración, utilicé los echocomandos anteriores; reemplácelos por exitdeclaraciones apropiadas para que coincidan con la función solicitada).


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
terdon

1
(Me pregunto por qué el último comentario no se movió al chat.) - Como ya se explicó a fondo y respaldado por las citas de POSIX en la discusión del chat (ver allí para más detalles); 1.) ("$(get_errnos)")es una adición artificial de una sustitución de comando en la que se solicitan comparaciones con códigos de error reales en la pregunta, 2.) En el estándar está claro qué cambia $?(y, por lo tanto, qué no lo cambia), y 3.) allí no hay efectos secundarios con esa construcción (a menos que, como lo hiciste, los introduzcas innecesariamente). - OTOH, ya que te importa, la otra respuesta que prefieres es claramente no estándar.
Janis

1
@mikeserv; ¡Es molesto (y engañoso) no solo porque aplica doble rasero ! Si aplica el mismo $(get_errnos)código artificial a cualquier otra solución ( ( exit 42 ); test "$(get_errnos)" -ne $? && echo $_), tampoco funcionan. (Prefirió incluir mi solución estándar en crédito erróneo, no los otros hacks no estándar .) - Por supuesto, puede agregar código arbitrario a cualquier respuesta y estropearlo. - Y WRT al cambio de $?; solo porque no puedas encontrarlo no significa que no sea cierto. (Ya proporcioné en nuestras discusiones incluso las palabras clave para buscar en POSIX.)
Janis

1
@mikeserv; Pero esto es, nuevamente, propenso a continuar la discusión (infructuosa e interminable), que no debe colocarse aquí como @terdon observó correctamente.
Janis

1
@mikeserv; Dije bastante al principio donde mencionaste por primera vez zsh(primero mencionado solo; luego agregaste también dash) que creo que debe haber un error allí, dado que el caseestado de salida y la configuración de $?parece estar bien definido en POSIX. - Y también aquí, de nuevo, aplica doble rasero; el otro truco, por ejemplo, no es estándar, ni funciona de manera confiable en otros shells estándar (por ejemplo, no en ksh). - Mis propuestas son estándar y funcionan bash(utilizadas principalmente en Linux) y ksh(el shell predominante en Unixes comerciales).
Janis

7
command -p ...
test 1 -ne $? && exit $_

Uso $_, que se expande al último argumento del comando anterior.


1
Válido para este ejemplo en particular, pero solo se puede usar si no hay otro comando entre las referencias $?y $_. En mi humilde opinión, es mejor seguir un método consistente que funciona en otros casos (y también puede ayudar con la legibilidad del código).
Dan Cornilescu

44
El shell se nombró específicamente dos veces, una en el título y otra como etiqueta. Tampoco se mencionó la portabilidad en ninguna parte de la pregunta, por lo tanto, di una respuesta que funciona en dicho shell. ¿Qué otras restricciones extrañas se van a inventar?
llua

1
@mikeserv; En caso de que te lo hayas perdido: dije: "Hubo un primer comentario aquí sobre POSIX". que tuvo que ser corregido. Ni mas ni menos. - Como se debatió a fondo con usted y se explicó allí, POSIX define bien las tres sugerencias en la otra respuesta. No es necesario repetir su opinión (IMO incorrecta) aquí, o comenzar otra iteración de la disputa.
Janis

1
@mikeserv; Los efectos secundarios de expansión en los case patrones , el único lugar que importaría en teoría (pero no en la pregunta dada), es un caso construido. - En cualquier caso, la sustitución de su comando de shell propagaría el resultado del comando incrustado, por lo general, otras expansiones no afectarán el estado de retorno de todos modos si no hay comandos involucrados que puedan crear errores ( x=${a!b}casos mentales , pero irrelevantes aquí). - ¿Qué quieres decir con "comando sin nombre de comando" ?
Janis

1
@mikeserv; Solo se puede bloquear con un código innecesario compuesto ad hoc. No hay absolutamente ninguna área gris si acepta la sugerencia sin introducir innecesariamente tonterías artificiales. Los requisitos eran absolutamente claros en este caso: 1. ejecutar un comando, 2. verificar el código de salida, 3. devolver el código de salida. - ¿Lo entiendes? - Cambiaste ese requisito arbitrariamente para inventar un argumento.
Janis

6

Puede definir (y usar) una función de shell:

check_exit_status()
{
    [ "$1" -ne 1 ] && exit "$1"
}

Entonces

command -p sudo ...
check_exit_status "$?"

Podría decirse que esto es "hacer trampa", ya que hace una copia $?en la check_exit_statuslista de argumentos.

Esto puede parecer un poco incómodo, y lo es. (Eso a veces sucede cuando impone restricciones arbitrarias a los problemas). Esto puede parecer inflexible, pero no lo es. Puede hacer check_exit_statusmás complejo, agregando argumentos que le digan qué pruebas hacer en el valor de estado de salida.


0

Para responder a su pregunta directa, no, no es posible mantenerse $?inalterado. Y este es uno de esos casos en los que sospecho que te estás enfocando en el problema equivocado. Una variable temporal es la forma estándar y preferida para obtener el efecto que está buscando. Es tan estándar que sugeriría abandonar (o repensar) cualquier razón que piense que tiene para no querer usar una; Dudo mucho que valga la complejidad adicional agregada por cualquier otro método.

Si solo pregunta por simple curiosidad, entonces la respuesta es no.


@mikeserv No estoy seguro de entender: ¿estás hablando de asignar manualmente $?o algo así?
David Z

0

Aquí está mi fragmento de código para retener un estado de salida anterior sin salir del script / shell actual

EXIT_STATUS=1
if [[ $EXIT_STATUS == 0 ]]
then
  echo yes
else
  (exit $EXIT_STATUS)
fi
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.