programacion BASH: haciéndose eco del último comando ejecutado




programacion shell linux ejemplos (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.


Answer #1

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

Answer #2

El historial de comandos es una función interactiva. Solo los comandos completos se ingresan en el historial. Por ejemplo, la construcción del case se ingresa como un todo, cuando el shell ha terminado de analizarlo. Ni buscar la historia con la history incorporada (ni imprimirla a través de la expansión de shell ( !:p )) hace lo que parece querer, que es imprimir invocaciones de comandos simples.

La trampa DEBUG permite ejecutar un comando justo antes de cualquier ejecución de comando simple. Una versión de cadena del comando para ejecutar (con palabras separadas por espacios) está disponible en la variable BASH_COMMAND .

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 desea conocer el estado de retorno del comando anterior también, 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 cancelar un comando fallido, use set -e para hacer que su script salga del primer comando fallido. Puede visualizar el último comando de la trampa EXIT .

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

Tenga en cuenta que si está intentando rastrear su script para ver qué está haciendo, olvídese de todo esto y use set -x .


Answer #3

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)


Answer #4

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






command