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.

246voto

groovyspaceman Points 454

Bash a des fonctionnalités intégrées pour accéder à la dernière commande exécutée. Mais il s'agit de la dernière commande entière (par exemple, toute la commande case ), et non des commandes individuelles simples comme vous l'aviez demandé à 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, l'expansion de l'historique est désactivée par défaut, vous devez l'activer à l'aide de la commande

set -o history -o histexpand

16 votes

Le cas d'utilisation le plus utile que j'ai vu est pour réexécution de la dernière commande avec un accès sudo c'est-à-dire sudo !!

4 votes

Avec set -o history -o histexpand; echo "!!" dans un script bash, j'obtiens toujours le message d'erreur : !!: event not found (C'est la même chose sans les guillemets).

2 votes

set -o history -o histexpand dans les scripts -> sauveteur ! merci !

69voto

Gilles Points 37537

L'historique des commandes est une fonction interactive. Seules les commandes complètes sont saisies dans l'historique. Par exemple, la commande case est saisie dans son ensemble, lorsque le shell a fini de l'analyser. Ni la recherche de l'historique avec la fonction history intégré (ni l'impression par expansion de l'interpréteur de commandes ( !:p )) fait ce que vous semblez vouloir, c'est-à-dire imprimer les invocations de commandes simples.

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

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 lancerez une commande, alors enregistrez-la dans une variable afin de pouvoir l'utiliser. Si vous souhaitez également connaître l'état de retour de la commande précédente, enregistrez les deux 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 voulez abandonner que sur une commande qui a échoué, utilisez set -e pour que votre script se termine à la première commande qui échoue. Vous pouvez afficher la dernière commande de la EXIT piège .

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

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

1 votes

J'ai essayé votre piège DEBUG mais je n'arrive pas à le faire fonctionner, pouvez-vous fournir un exemple complet s'il vous plaît ? -x affiche chaque commande, mais malheureusement, je ne suis intéressé que par les commandes qui échouent (ce que je peux faire avec ma commande si je la place à l'intérieur d'un fichier de type [ ! "$? == "0" ] déclaration.

0 votes

@user359650 : Corrigé. Vous devez avoir sauvegardé la commande précédente avant qu'elle ne soit écrasée par la commande actuelle. Pour interrompre votre script si une commande échoue, utilisez set -e (souvent, mais pas toujours, la commande produira un message d'erreur suffisamment bon pour que vous n'ayez pas besoin de fournir un contexte supplémentaire).

0 votes

Merci pour votre contribution. J'ai fini par écrire une fonction personnalisée (voir mon message) car votre solution était trop lourde.

29voto

Hercynium Points 352

Après avoir lu le réponse de Gilles j'ai décidé de voir si le $BASH_COMMAND était également disponible (et la valeur souhaitée) dans un fichier EXIT piège - et il l'est !

Ainsi, le bash script 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"

Le résultat est

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

bar n'est jamais imprimé car set -e fait que bash quitte le script lorsqu'une commande échoue et que la fausse commande échoue toujours (par définition). L'adresse 12345 transmis à false est juste là pour montrer que les arguments de la commande qui a échoué sont également capturés (l'élément false ignore les arguments qui lui sont passés)

2 votes

C'est absolument la meilleure solution. Cela fonctionne comme un charme pour moi avec "set -euo pipefail".

11voto

Mark Drago Points 801

J'y suis parvenu en utilisant set -x dans le script principal (qui fait en sorte que le script imprime chaque commande exécutée) et en écrivant un script enveloppant qui montre juste la dernière ligne de sortie générée par set -x .

C'est le principal script :

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

Et voici l'enveloppe script :

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

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

echo last command

0 votes

Cela semble être une bonne approche. Un post sur votre profil pour plus de détails ? Tx.

9voto

Guma Points 105

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

tail -2 retourne 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.

1 votes

Pourriez-vous expliquer un peu quels sont les arguments des commandes ? Cela aiderait à comprendre ce que vous avez fait.

0 votes

Bien que cela puisse répondre à la question, il est préférable d'ajouter une description de la manière dont cette réponse peut aider à résoudre le problème. Veuillez lire Comment rédiger une bonne réponse pour en savoir plus.

0 votes

Rapide et super facile. Tx.

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