Estoy tratando de hacer eco del último comando ejecutado dentro de un script bash. Encontré una manera de hacerlo con algunos history,tail,head,sed
que funcionan bien cuando los comandos representan una línea específica en mi script desde el punto de vista del analizador. Sin embargo, en algunas circunstancias no obtengo el resultado esperado, por ejemplo, cuando el comando se inserta dentro de una case
declaración:
La secuencia de comandos:
#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
case "1" in
"1")
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
;;
esac
La salida:
Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]
[P] ¿Alguien puede ayudarme a encontrar una manera de repetir el último comando de ejecución independientemente de cómo / dónde se llame este comando dentro del script bash?
Mi respuesta
A pesar de las contribuciones muy apreciadas de mis compañeros SO'ers, opté por escribir una run
función, que ejecuta todos sus parámetros como un solo comando y muestra el comando y su código de error cuando falla, con los siguientes beneficios:
-Sólo necesito anteponer los comandos que quiero verificar con lo run
que los mantiene en una línea y no afecta la concisión de mi script
-Siempre que el script falla en uno de estos comandos, la última línea de salida de mi script es un mensaje que muestra claramente qué comando falla junto con su código de salida, lo que facilita la depuración
Script de ejemplo:
#!/bin/bash
die() { echo >&2 -e "\nERROR: $@\n"; exit 1; }
run() { "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }
case "1" in
"1")
run ls /opt
run ls /wrong-dir
;;
esac
La salida:
$ ./test.sh
apacheds google iptables
ls: cannot access /wrong-dir: No such file or directory
ERROR: command [ls /wrong-dir] failed with error code 2
Probé varios comandos con múltiples argumentos, variables bash como argumentos, argumentos entre comillas ... y la run
función no los rompió. El único problema que encontré hasta ahora es ejecutar un eco que se rompe, pero no planeo comprobar mis ecos de todos modos.
run()
no funciona correctamente cuando se utilizan comillas, por ejemplo, esto no funciona:run ssh-keygen -t rsa -C [email protected] -f ./id_rsa -N ""
."something"
argumentos con'"something"'
(o, más bien,"'something'"
para permitirsomething
(por ejemplo: variables) que se interpreten / evalúen en el primer nivel, si es necesario)run() { $*; … }
a uno más casi correctorun() { "$@"; … }
porque la respuesta errónea terminó produciendocp
salidas de preguntas con un estado de error 64 , donde el problema era que$*
rompían los argumentos del comando en los espacios en los nombres, pero"$@"
no lo haría.last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')
funcionó mejor, al menos para zsh y macOS 10.11Respuestas:
El historial de comandos es una función interactiva. Solo los comandos completos se ingresan en el historial. Por ejemplo, la
case
construcción se ingresa como un todo, cuando el shell ha terminado de analizarlo. Ni buscar el historial con elhistory
incorporado (ni imprimirlo a través de la expansión de shell (!:p
)) hace lo que parece querer, que es imprimir invocaciones de comandos simples.La
DEBUG
trampa le permite ejecutar un comando justo antes de ejecutar cualquier comando simple. Una versión de cadena del comando para ejecutar (con palabras separadas por espacios) está disponible en laBASH_COMMAND
variable.trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG … echo "last command is $previous_command"
Tenga en cuenta que
previous_command
cambiará cada vez que ejecute un comando, así que guárdelo en una variable para poder usarlo. Si también desea conocer el estado de retorno del comando anterior, guarde ambos en un solo comando.cmd=$previous_command ret=$? if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi
Además, si solo desea abortar en un comando fallido, use
set -e
para hacer que su script salga en el primer comando fallido. Puede mostrar el último comando de laEXIT
trampa .set -e trap 'echo "exit $? due to $previous_command"' EXIT
Tenga en cuenta que si está tratando de rastrear su secuencia de comandos para ver qué está haciendo, olvide todo esto y use
set -x
.fuente
-x
genera todos los comandos, pero desafortunadamente solo estoy interesado en ver los comandos que fallan (lo que puedo lograr con mi comando si lo coloco dentro de una[ ! "$? == "0" ]
declaración.set -e
(a menudo, pero no siempre, el comando producirá un mensaje de error suficientemente bueno para que no necesite proporcionar más contexto).eval echo "${BASH_COMMAND}"
podría ejecutar código arbitrario en sustituciones de comandos. Es peligroso. Considere un comando comocd $(ls -td | head -n 1)
- y ahora imagine la sustitución de comando llamadarm
o algo así.Bash tiene funciones integradas para acceder al último comando ejecutado. Pero ese es el último comando completo (por ejemplo, el
case
comando completo ), no comandos simples individuales como solicitó originalmente.!:0
= el nombre del comando ejecutado.!:1
= el primer parámetro del comando anterior!:*
= todos los parámetros del comando anterior!:-1
= el parámetro final del comando anterior!!
= la línea de comando anterioretc.
Entonces, la respuesta más simple a la pregunta es, de hecho:
echo !!
...alternativamente:
echo "Last command run was ["!:0"] with arguments ["!:*"]"
¡Inténtalo tú mismo!
echo this is a test echo !!
En un script, la expansión del historial está desactivada de manera predeterminada, debe habilitarla con
set -o history -o histexpand
fuente
sudo !!
set -o history -o histexpand; echo "!!"
un script de bash todavía recibo el mensaje de error:!!: event not found
(Es lo mismo sin las comillas).set -o history -o histexpand
en guiones -> salvavidas! ¡Gracias!bash
sigue imprimiendo !! en lugar del último comando de ejecución. ¿Dónde está documentado esto?Después de leer la respuesta de Gilles , decidí ver si la
$BASH_COMMAND
var también estaba disponible (y el valor deseado) en unaEXIT
trampa, ¡y lo está!Entonces, el siguiente script bash funciona como se esperaba:
#!/bin/bash exit_trap () { local lc="$BASH_COMMAND" rc=$? echo "Command [$lc] exited with code [$rc]" } trap exit_trap EXIT set -e echo "foo" false 12345 echo "bar"
La salida es
foo Command [false 12345] exited with code [1]
bar
nunca se imprime porqueset -e
hace que bash salga del script cuando falla un comando y el comando falso siempre falla (por definición). El12345
paso afalse
está ahí para mostrar que los argumentos del comando fallido también se capturan (elfalse
comando ignora cualquier argumento que se le pase)fuente
Pude lograr esto usando
set -x
en el script principal (que hace que el script imprima cada comando que se ejecuta) y escribiendo un script contenedor que solo muestra la última línea de salida generada porset -x
.Este es el guión principal:
#!/bin/bash set -x echo some command here echo last command
Y este es el script de envoltura:
#!/bin/sh ./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'
La ejecución del script de envoltura produce esto como salida:
echo last command
fuente
history | tail -2 | head -1 | cut -c8-999
tail -2
devuelve las dos últimas líneas de comando del historialhead -1
devuelve solo la primera líneacut -c8-999
devuelve solo la línea de comando, eliminando PID y espacios.fuente
Existe una condición de carrera entre las variables de último comando ($ _) y último error ($?). Si intenta almacenar uno de ellos en una variable propia, ambos ya encontraron nuevos valores debido al comando set. En realidad, el último comando no tiene ningún valor en este caso.
Esto es lo que hice para almacenar (casi) ambas informaciones en variables propias, por lo que mi script bash puede determinar si hubo algún error Y establecer el título con el último comando de ejecución:
# This construct is needed, because of a racecondition when trying to obtain # both of last command and error. With this the information of last error is # implied by the corresponding case while command is retrieved. if [[ "${?}" == 0 && "${_}" != "" ]] ; then # Last command MUST be retrieved first. LASTCOMMAND="${_}" ; RETURNSTATUS='✓' ; elif [[ "${?}" == 0 && "${_}" == "" ]] ; then LASTCOMMAND='unknown' ; RETURNSTATUS='✓' ; elif [[ "${?}" != 0 && "${_}" != "" ]] ; then # Last command MUST be retrieved first. LASTCOMMAND="${_}" ; RETURNSTATUS='✗' ; # Fixme: "$?" not changing state until command executed. elif [[ "${?}" != 0 && "${_}" == "" ]] ; then LASTCOMMAND='unknown' ; RETURNSTATUS='✗' ; # Fixme: "$?" not changing state until command executed. fi
Este script retendrá la información, si ocurrió un error y obtendrá el último comando de ejecución. Debido a la condición de carrera, no puedo almacenar el valor real. Además, la mayoría de los comandos ni siquiera se preocupan por los números de error, solo devuelven algo diferente de '0'. Notarás que, si usas la extensión errono de bash.
Debería ser posible con algo así como un script "interno" para bash, como en la extensión de bash, pero no estoy familiarizado con algo así y tampoco sería compatible.
CORRECCIÓN
No pensé que fuera posible recuperar ambas variables al mismo tiempo. Aunque me gusta el estilo del código, asumí que se interpretaría como dos comandos. Esto estaba mal, por lo que mi respuesta se divide en:
# Because of a racecondition, both MUST be retrieved at the same time. declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ; if [[ "${RETURNSTATUS}" == 0 ]] ; then declare RETURNSYMBOL='✓' ; else declare RETURNSYMBOL='✗' ; fi
Aunque es posible que mi publicación no obtenga una calificación positiva, finalmente resolví mi problema yo mismo. Y esto parece apropiado con respecto a la publicación inicial. :)
fuente