114 votes

L'écho de la dernière commande exécutée dans Bash ?

J'essaie de faire écho à la dernière commande exécutée à l'intérieur d'un script bash. J'ai trouvé un moyen de le faire avec des history,tail,head,sed ce 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 n'obtiens pas la sortie attendue, par exemple lorsque la commande est insérée à l'intérieur d'un fichier case déclaration :

Le script :

#!/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] Quelqu'un peut-il m'aider à trouver un moyen de faire écho à la dernière commande exécutée indépendamment de la façon dont/où cette commande est appelée dans le script de bash ?

Ma réponse

Malgré les contributions très appréciées de mes collègues du SO, j'ai opté pour la rédaction d'une run qui exécute tous ses paramètres comme une seule commande et affiche la commande et son code d'erreur en cas d'échec - avec les avantages suivants :
-Il suffit de faire précéder les commandes que je veux vérifier de la mention run ce qui les garde sur une seule ligne et n'affecte pas la concision de mon script.
-Lorsque le script échoue sur l'une de ces commandes, la dernière ligne de sortie de mon script est un message qui affiche clairement quelle commande a échoué ainsi que son code de sortie, ce qui facilite le débogage.

Exemple 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 des arguments multiples, des variables bash en tant qu'arguments, des arguments entre guillemets... et le résultat est le suivant run ne les a pas cassés. Le seul problème que j'ai trouvé jusqu'à présent est de lancer un écho qui se casse, mais je ne prévois pas de vérifier mes échos de toute façon.

1 votes

+1, idée brillante ! Notez cependant que run() ne fonctionne pas correctement lorsque des guillemets sont utilisés, par exemple ceci échoue : run ssh-keygen -t rsa -C info@example.org -f ./id_rsa -N "" .

0 votes

@johndodo : cela pourrait être corrigé : il suffit de changer "something" dans les discussions avec '"something"' (ou, plutôt, "'something'" pour permettre something (ex : variables) à interpréter/évaluer au premier niveau, si nécessaire)

2 votes

J'ai changé l'erreur run() { $*; … } en une version plus proche de la réalité run() { "$@"; … } parce que la réponse erronée a fini par donner la question cp se termine avec un statut d'erreur de 64 où le problème était que le $* a cassé les arguments de la commande au niveau des espaces dans les noms, mais "$@" ne le ferait pas.

3voto

WGRM Points 111

Il existe 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'entre elles dans une variable propre, les deux ont déjà trouvé de nouvelles valeurs à cause de la commande set. En fait, la dernière commande n'a pas de valeur du tout dans ce cas.

Voici ce que j'ai fait pour stocker (presque) les deux informations dans des variables propres, afin que mon bash script puisse déterminer s'il y a eu une erreur ET définir 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 l'information, si une erreur s'est produite et obtiendra la dernière commande d'exécution. A cause de la racecondition, je ne peux pas stocker la valeur réelle. De plus, la plupart des commandes ne se soucient pas des symboles d'erreur, elles renvoient simplement quelque chose de différent de '0'. Vous remarquerez que, si vous utilisez l'extension errono de bash.

Cela devrait être possible avec quelque chose comme un script "interne" pour bash, comme dans bash extention, mais je ne suis pas familier avec quelque chose comme ça et ce ne serait pas compatible aussi bien.

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, donc ma réponse se résume à :

   # 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 n'obtienne pas d'évaluation positive, j'ai finalement résolu mon problème moi-même. Et cela semble approprié par rapport au message initial :)

1 votes

Oh là là, il suffit de les recevoir en une fois et cela semble possible : declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ;

0 votes

Fonctionne très bien à une exception près. Si j'ai un alias pour d'autres paramètres, il affiche simplement les paramètres. Quelqu'un a-t-il des conclusions ?

0 votes

On dirait une ligne de base flexible. Je dois le tester !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X