web-dev-qa-db-fra.com

BASH: faisant écho à la dernière commande exécutée

J'essaie de faire écho à la dernière commande exécutée dans un script bash. J'ai trouvé un moyen de le faire avec un history,tail,head,sed qui fonctionne bien lorsque les commandes représentent une ligne spécifique dans mon script du point de vue de l'analyseur. Cependant, dans certaines circonstances, je ne reçois pas le résultat attendu, par exemple lorsque la commande est insérée dans une instruction case:

Le scénario:

#!/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

Le résultat:

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]"]

[Q] Est-ce que quelqu'un peut m'aider à trouver un moyen de faire écho à la dernière commande exécutée, peu importe comment/où cette commande est appelée dans le script bash?

Ma réponse

Malgré les contributions très appréciées de mes collègues SO, j'ai opté pour l'écriture d'une fonction run - qui exécute tous ses paramètres en tant que commande unique et affiche la commande ainsi que son code d'erreur en cas d'échec - avec les avantages suivants:
- Je dois seulement ajouter les commandes que je veux vérifier avec run qui les garde sur une seule ligne et qui n’affecte pas la concision de mon script
- Chaque fois que le script échoue sur l'une de ces commandes, la dernière ligne de sortie de mon script est un message indiquant clairement quelle commande échoue avec son code de sortie, ce qui facilite le débogage.

Exemple de script:

#!/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

Le résultat:

$ ./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

J'ai testé diverses commandes avec plusieurs arguments, les variables bash comme arguments, les arguments cités ... et la fonction run ne les a pas cassées. Le seul problème que j’ai trouvé jusqu’à présent est de courir un écho qui se rompt mais je n’ai pas l’intention de vérifier mes échos de toute façon.

67
Max

L'historique des commandes est une fonctionnalité interactive. Seules les commandes complètes sont entrées dans l'historique. Par exemple, la construction case est entrée dans son ensemble lorsque le shell a fini de l’analyser. Ni consulter l'historique avec la variable history intégrée (ni l'imprimer via l'extension Shell (!:p)) ne fait ce que vous semblez vouloir, à savoir imprimer des invocations de commandes simples.

Le piège DEBUG vous permet d'exécuter une commande juste avant toute exécution de commande simple. Une version chaîne de la commande à exécuter (avec des mots séparés par des espaces) est disponible dans la variable BASH_COMMAND .

trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"

Notez que previous_command changera chaque fois que vous exécuterez une commande, alors enregistrez-la dans une variable pour pouvoir l'utiliser. Si vous souhaitez également connaître le statut de retour de la commande précédente, enregistrez-les dans une seule commande.

cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi

De plus, si vous ne souhaitez abandonner que les commandes ayant échoué, utilisez set -e pour que votre script quitte la première commande ayant échoué. Vous pouvez afficher la dernière commande à partir du EXIT trap .

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

Notez que si vous essayez de suivre votre script pour voir ce qu'il fait, oubliez tout cela et utilisez set -x .

52
Gilles

Bash a des fonctionnalités intégrées pour accéder à la dernière commande exécutée. Mais c’est la dernière commande complète (par exemple, la commande entière case), et non des commandes simples telles que demandées à l’origine.

!:0 = le nom de la commande exécutée.

!:1 = le premier paramètre de la commande précédente

!:* = tous les paramètres de la commande précédente

!:-1 = le dernier paramètre de la commande précédente

!! = la ligne de commande précédente

etc.

Donc, la réponse la plus simple à la question est, en fait:

echo !!

... alternativement: 

echo "Last command run was ["!:0"] with arguments ["!:*"]"

Essayez vous-même!

echo this is a test
echo !!

Dans un script, le développement de l'historique est désactivé par défaut, vous devez l'activer avec

set -o history -o histexpand
143
groovyspaceman

Après avoir lu le answer de Gilles , j'ai décidé de voir si le $BASH_COMMAND var était également disponible (et la valeur souhaitée) dans un piège EXIT - et c'est le cas!

Ainsi, le script bash suivant fonctionne comme prévu:

#!/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 sortie est

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

bar n'est jamais imprimé car set -e amène bash à quitter le script lorsqu'une commande échoue et que la commande false échoue toujours (par définition). Le 12345 transmis à false est juste là pour montrer que les arguments de la commande ayant échoué sont également capturés (la commande false ignore tous les arguments qui lui sont transmis)

13
Hercynium

Pour ce faire, j'ai utilisé set -x dans le script principal (ce qui fait en sorte que le script imprime chaque commande exécutée) et en écrivant un script wrapper qui affiche simplement la dernière ligne de sortie générée par set -x.

C'est le script principal:

#!/bin/bash
set -x
echo some command here
echo last command

Et voici le script wrapper:

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

L'exécution du script wrapper produit ceci en sortie:

echo last command
7
Mark Drago

history | tail -2 | head -1 | cut -c8-999

tail -2 renvoie les deux dernières lignes de commande de l'historique head -1 renvoie uniquement la première ligne cut -c8-999 renvoie uniquement la ligne de commande, en supprimant le PID et les espaces.

0
Guma

Il y a une condition de course entre les variables de la dernière commande ($ _) et de la dernière erreur ($?). Si vous essayez de stocker l'une d'elles dans une propre variable, les deux ont déjà rencontré de nouvelles valeurs à cause de la commande set. En fait, la dernière commande n'a aucune valeur dans ce cas.

Voici ce que j'ai fait pour stocker (presque) les deux informations dans des variables propres, afin que mon script bash puisse déterminer s'il y avait une erreur ET en définissant le titre avec la dernière commande d'exécution:

   # 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

Ce script conservera les informations en cas d'erreur et obtiendra la dernière commande d'exécution. En raison de la condition de rachat, je ne peux pas stocker la valeur réelle. En outre, la plupart des commandes ne s’inquiètent même pas des erreurs, elles renvoient simplement quelque chose de différent de '0'. Vous remarquerez que si vous utilisez l'extension erronée de bash.

Cela devrait être possible avec quelque chose comme un script "interne" pour bash, comme dans l'extension bash, mais je ne suis pas familier avec quelque chose comme ça et ce ne serait pas compatible non plus.

CORRECTION

Je ne pensais pas qu'il était possible de récupérer les deux variables en même temps. Bien que j'aime le style du code, j'ai supposé qu'il serait interprété comme deux commandes. C'était faux, ma réponse est donc la suivante:

   # 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

Bien que mon message puisse ne pas recevoir d’évaluation positive, j’ai résolu moi-même mon problème, enfin… .. Et cela semble approprié pour le message initial. :)

0
WGRM