web-dev-qa-db-fra.com

Quel est un moyen pratique d'exécuter une longue liste de commandes et d'afficher un message en cas de problème?

La plupart des guides Linux se composent de pages telles que "vous devez exécuter command_1, puis command_2, puis command_3 "etc. Comme je ne veux pas perdre mon temps à les exécuter tous manuellement, je préfère créer un script

command_1
command_2
command_3

et l'exécuter une fois. Mais, le plus souvent, certaines commandes échouent et je n'ai aucune idée de celles qui ont échoué. De plus, généralement toutes les commandes restantes n'ont aucun sens si quelque chose échoue plus tôt. Donc un meilleur script serait quelque chose comme

   (command_1 && echo OK command_1 || (echo FAILED command_1; false) )
&& (command_2 && echo OK command_2 || (echo FAILED command_2; false) )
&& (command_3 && echo OK command_3 || (echo FAILED command_3; false) )
&& echo DONE 
|| echo FAILED

Mais cela nécessite d'écrire trop de code passe-partout, de répéter chaque commande 3 fois, et il y a trop de chances que je frappe mal certains accolades. Existe-t-il un moyen plus pratique de faire ce que fait le dernier script? En particulier:

  • exécuter les commandes séquentiellement
  • interrompre si une commande échoue
  • écrire, quelle commande a échoué, le cas échéant
  • Permet des interactions normales avec les commandes: imprime toutes les sorties et autorise la saisie à partir du clavier, si la commande demande quelque chose.

Résumé des réponses (2 janvier 2020)

Il existe 2 types de solutions:

  • Ceux qui permettent de copier-coller des commandes du guide sans modifications, mais ils n'impriment pas la commande échouée à la fin. Donc, si la commande ayant échoué a produit une sortie très longue, vous devrez faire défiler un grand nombre de lignes pour voir quelle commande a échoué. (Toutes les meilleures réponses)
  • Celles qui affichent la commande ayant échoué dans la dernière ligne, mais vous obligent à modifier les commandes après les avoir copiées-collées, soit en ajoutant des citations (réponse de John), soit en ajoutant des instructions try et en divisant les commandes chaînées en différentes ceux (réponse de Jasen).

Vous êtes rock, mais je vais laisser cette question ouverte pendant un moment. Peut-être que quelqu'un connaît une solution qui satisfait les deux besoins (imprimer la commande échouée sur la dernière ligne et autoriser le copier-coller des commandes sans leurs modifications).

33
Arqwer

Une option serait de placer les commandes dans un script bash et de le démarrer avec set -e.

Cela entraînera la fin prématurée du script si une commande se termine avec un état de sortie différent de zéro.

Voir aussi cette question sur le débordement de pile: https://stackoverflow.com/q/19622198/82819

Pour imprimer l'erreur, vous pouvez utiliser

trap 'do_something' ERR

do_something est une commande que vous créeriez pour afficher l'erreur.

Voici un exemple de script pour voir comment cela fonctionne:

#!/bin/bash

set -e
trap 'echo "******* FAILED *******" 1>&2' ERR

echo 'Command that succeeds'   # this command works
ls non_existent_file           # this should fail
echo 'Unreachable command'     # and this is never called
                               # due to set -e

Et voici la sortie:

$ ./test.sh 
Command that succeeds
ls: cannot access 'non_existent_file': No such file or directory
******* FAILED *******

De plus, comme mentionné par @ jick , gardez à l'esprit que l'état de sortie d'un pipeline est par défaut l'état de sortie du final commande dedans. Cela signifie que si une commande non finale du pipeline échoue, elle ne sera pas interceptée par set -e. Pour résoudre ce problème si vous le souhaitez, vous pouvez utiliser set -o pipefail


Comme suggéré mon @ glenn jackman et @ Monty Harder , en utilisant une fonction car le gestionnaire peut rendre le script plus lisible, car il évite les guillemets imbriqués. Puisque nous utilisons une fonction de toute façon maintenant, j'ai supprimé set -e entièrement, et utilisé exit 1 dans le gestionnaire, ce qui pourrait également le rendre plus lisible pour certains:

#!/bin/bash

error_handler() {
  echo "******* FAILED *******" 1>&2
  exit 1
}

trap error_handler ERR

echo 'Command that succeeds'   # this command works
ls non_existent_file           # this should fail
echo 'Unreachable command'     # and this is never called
                               # due to the exit in the handler

La sortie est identique à celle ci-dessus, bien que l'état de sortie du script soit différent.

62
user000001

Vous voulez une solution inhabituelle? Si vous avez make installé, vous pouvez placer la liste des commandes dans un Makefile pour que make s'exécute. Avantage supplémentaire: vous n'avez pas à vérifier si des erreurs se sont produites. make vérifiera automatiquement la valeur de retour de chaque commande. S'il est différent de zéro, la recette se termine avec une erreur. Si vous souhaitez ignorer les erreurs avec certaines commandes, chaînez-les avec || true.

Exemple de Makefile:

.PHONY: all
.SILENT:

all:
    echo "Started list of commands."
    true
    echo "Executing a command which will fail, but I want to ignore failure."
    false || true
    echo "Executing a command which will definitely fail."
    false
    echo "This code will not be reached."

Remarque: assurez-vous (heh) de mettre en retrait vos commandes comme ci-dessus, mais avec un onglet.

20
Mukesh Sai Kumar

Je crois que cela ne fonctionne que pour bash, mais vous pouvez essayer:

set -o xtrace
set -o errexit

Ou, si vous voulez être concis,

set -ex

Cela fera deux choses: errexit (-e) abandonnera le script en cas d'erreur, et xtrace (-x) affichera chaque commande juste avant que bash l'exécute, donc au cas où d'échec, vous savez exactement ce qu'il exécutait.

Un inconvénient est qu'il encombrera la sortie, mais tant que vous êtes d'accord, c'est une assez bonne solution avec un travail minimal.

  • Pendant que nous y sommes, c'est généralement une bonne idée d'inclure également set -o pipefail: sinon, si vous exécutez foo | bar, l'échec de foo sera ignoré en silence. (Attention: il modifie subtilement le "code de sortie" d'une instruction pipe, donc utilisez-le avec prudence si vous modifiez le script de quelqu'un d'autre. De plus, parfois vous ne vous souciez pas de l'échec de foo, donc évidemment vous ne peut pas utiliser pipefail dans un tel cas.)
16
jick

Vous pouvez faire quelque chose comme ça:

$ for f in command_1 command_2 command_3 command_4
do
  $f
  rc=$?
  if [[ 0 != $rc ]]; then echo Failed command: ${f}; break; fi
done

Cela suppose que les commandes n'ont pas d'options, si c'est le cas, vous devrez placer chaque jeu de commandes/options entre guillemets.

3
John
failed=0
try(){
(( failed )) && return
"$@" 
ec=$?
if (( ec ))
then
  failed=$ec
  echo "failed($failed): $*" >&2
fi
}

try something args
try other-thing more args

if (( failed )) 
then
  echo "something went wrong: see above." >&2
  exit 1
fi
3
Jasen

Utilisez cette solution si vous souhaitez le faire dans le shell interactif:

set -x; command_1 && command_2 && command_3 && echo "Everything OK" || echo "Error while executing last command"; set +x

Sortie lorsque toutes les commandes réussissent:

+ command_1
output of command 1
+ command_2
output of command 2
+ command_3
output of command 3
+ echo "Everything OK"
Everything OK
+ set +x

Sortie lorsque le command_2 échoue:

+ command_1
output of command 1
+ command_2
output of command 2
+ echo "Error while executing last command"
+ set +x

Le set -x commande active la journalisation de toutes les commandes exécutées. Les commandes enregistrées sont préfixées par un ou plusieurs avantages (le style peut être modifié à l'aide de la variable $PS4). set +x désactive ce mode.

Vous devez mettre le set -x et set +x à la même ligne car set -x active la journalisation des commandes exécutées toutes - y compris $COMMAND_Prompt et les commandes qu'il exécute.


Vous pouvez également l'utiliser pour les scripts Shell:

#!/bin/bash -ex

command_1
command_2
command_3

Les options de shell peuvent être intégrées dans la ligne Shebang. -e fait quitter Shell après la première commande ayant échoué (comme lorsque toutes les lignes se terminent par &&). -x active la journalisation des commandes. Notez que vous ne pouvez passer que l'argument one à l'interpréteur. Utilisation /usr/bin/env pour passer plusieurs arguments (#!/usr/bin/env -S bash -ex arg1 arg2 argN).

3
jiwopene

Peut-être juste une fonction pour ce que vous faites déjà:

function run_cmd
    ( $* && echo OK $* || ( echo FAILED $*; false ) )

run_cmd echo 1 &&
run_cmd false 2 &&
run_cmd echo 3 &&
echo DONE || echo FAILED

La sortie de ceci est:

1
OK echo 1
FAILED false 2
FAILED
1
Pedja