bash - scripts - shell script linux español




BASH: haciéndose eco del último comando ejecutado (4)

Intento repetir el último comando ejecutado dentro de un script bash. Encontré una manera de hacerlo con un poco de history,tail,head,sed que funciona 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 declaración de case :

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 forma de repetir el último comando de ejecución independientemente de cómo / dónde se llama 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 función de run , 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:
-Solo necesito anteponer los comandos que quiero verificar con 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 hace que la depuración sea más fácil.

Script de ejemplo:

#!/bin/bash
die() { echo >&2 -e "\nERROR: [email protected]\n"; exit 1; }
run() { "[email protected]"; 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 de bash como argumentos, argumentos entrecomillados ... y la función de run no los rompió. El único problema que encontré hasta ahora es ejecutar un eco que se rompe pero de todos modos no planeo revisar mis ecos.


Bash ha incorporado funciones para acceder al último comando ejecutado. Pero ese es el último comando completo (por ejemplo, el comando de todo el case ), no comandos simples individuales como los solicitados 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 anterior

etc.

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 una secuencia de comandos, la expansión de historial está desactivada de manera predeterminada, debe habilitarla con

set -o history -o histexpand

Después de leer la answer de , decidí ver si la var $BASH_COMMAND también estaba disponible (y el valor deseado) en una trampa EXIT , ¡y lo es!

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"

El resultado es

foo
Command [false 12345] exited with code [1]

bar nunca se imprime porque el set -e hace que bash salga de la secuencia de comandos cuando falla una orden y la orden falsa siempre falla (por definición). El 12345 pasado a false está justo allí para mostrar que los argumentos al comando fallido también se capturan (el comando false ignora cualquier argumento que se le haya pasado)


Existe una condición de carrera entre las últimas variables de 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 ocurre 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 realmente no se preocupan por los números de error, simplemente devuelven algo diferente de '0'. Lo notarás si usas la extensión errono de bash.

Debería ser posible con algo así como un guión "interno" para bash, como en la extensión bash, pero no estoy familiarizado con algo así y tampoco sería compatible.

CORRECCIÓN

No pensé que era posible recuperar ambas variables al mismo tiempo. Aunque me gusta el estilo del código, supuse que sería interpretado como dos comandos. Esto fue incorrecto, entonces mi respuesta se reduce a:

   # 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 mi publicación podría no obtener una calificación positiva, finalmente resolví mi problema yo mismo. Y esto parece apropiado con respecto a la publicación inicial. :)


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 de contenedor que solo muestra la última línea de salida generada por set -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/^\+ //'

Ejecutar el script de envoltura lo produce como salida:

echo last command




command