Supprimer la trace d'exécution pour la commande echo?


29

J'exécute des scripts shell à partir de Jenkins, qui lance des scripts shell avec les options shebang #!/bin/sh -ex.

Selon Bash Shebang pour les nuls? , -x« Provoque la coquille d'imprimer une trace d'exécution », ce qui est excellent pour la plupart des - à l' exception des echos:

echo "Message"

produit la sortie

+ echo "Message"
Message

ce qui est un peu redondant et semble un peu étrange. Existe-t-il un moyen de laisser -xactivé, mais uniquement la sortie

Message

au lieu des deux lignes ci-dessus, par exemple en préfixant la commande echo avec un caractère de commande spécial, ou en redirigeant la sortie?

Réponses:


20

Lorsque vous êtes à la hauteur de votre cou en alligators, il est facile d'oublier que le but était de drainer le marais.                   - dicton populaire

La question porte sur echo, et pourtant la majorité des réponses jusqu'à présent se sont concentrées sur la façon d'introduire une set +xcommande. Il existe une solution beaucoup plus simple et plus directe:

{ echo "Message"; } 2> /dev/null

(Je reconnais que je n'aurais peut-être pas pensé au { …; } 2> /dev/null si je ne l'avais pas vu dans les réponses précédentes.)

C'est un peu lourd, mais si vous avez un bloc de echocommandes consécutives , vous n'avez pas besoin de le faire sur chacune d'elles individuellement:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Notez que vous n'avez pas besoin de points-virgules lorsque vous avez des retours à la ligne.

Vous pouvez réduire la charge de frappe en utilisant l'idée de kenorb d'ouvrir en /dev/nullpermanence sur un descripteur de fichier non standard (par exemple, 3), puis en disant 2>&3au lieu de 2> /dev/nulltout le temps.


Les quatre premières réponses au moment d'écrire ces lignes nécessitent de faire quelque chose de spécial (et, dans la plupart des cas, lourd) à chaque fois que vous faites un echo. Si vous voulez vraiment que toutes les echo commandes suppriment la trace d'exécution (et pourquoi pas?), Vous pouvez le faire globalement, sans munging beaucoup de code. Tout d'abord, j'ai remarqué que les alias ne sont pas tracés:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Notez que les extraits de script suivants fonctionnent si le shebang est #!/bin/sh, même s'il /bin/shs'agit d'un lien vers bash. Mais, si le shebang l'est #!/bin/bash, vous devez ajouter une shopt -s expand_aliasescommande pour que les alias fonctionnent dans un script.)

Donc, pour mon premier tour:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Maintenant, quand nous disons echo "Message", nous appelons l'alias, qui n'est pas tracé. L'alias désactive l'option de trace, tout en supprimant le message de trace de la setcommande (en utilisant la technique présentée en premier dans la réponse de user5071535 ), puis exécute la echocommande réelle . Cela nous permet d'obtenir un effet similaire à celui de la réponse de user5071535 sans avoir à modifier le code à chaque echo commande. Cependant, cela laisse le mode trace désactivé. Nous ne pouvons pas mettre un set -xdans l'alias (ou du moins pas facilement) car un alias permet uniquement de substituer une chaîne à un mot; aucune partie de la chaîne d'alias ne peut être injectée dans la commande après les arguments (par exemple, "Message"). Ainsi, par exemple, si le script contient

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

la sortie serait

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

vous devez donc réactiver l'option trace après avoir affiché le (s) message (s) - mais seulement une fois après chaque bloc de echocommandes consécutives :

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Ce serait bien si nous pouvions faire l' set -xautomatique après un echo- et nous pouvons, avec un peu plus de ruse. Mais avant de présenter cela, réfléchissez à cela. L'OP commence avec des scripts qui utilisent un #!/bin/sh -exshebang. Implicitement, l'utilisateur pourrait supprimer le xdu shebang et avoir un script qui fonctionne normalement, sans traçage d'exécution. Ce serait bien si nous pouvions développer une solution qui conserve cette propriété. Les premières réponses ici échouent à cette propriété car elles réactivent le traçage des echodéclarations après , sans condition, sans se soucier de savoir si c'était déjà le cas.  Cette réponse ne reconnaît visiblement pas ce problème, car elle remplace echosortie avec sortie trace; par conséquent, tous les messages disparaissent si le suivi est désactivé. Je vais maintenant présenter une solution qui réactivera le traçage après une echodéclaration conditionnellement - uniquement si elle était déjà activée. La rétrogradation à une solution qui active le «retour» du traçage sans condition est triviale et est laissée comme exercice.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-est la liste des options; une concaténation des lettres correspondant à toutes les options définies. Par exemple, si les options eet xsont définies, alors $-sera un mélange de lettres qui comprend eet x. Mon nouvel alias (ci-dessus) enregistre la valeur de $-avant de désactiver le traçage. Ensuite, avec le suivi désactivé, il jette le contrôle sur une fonction shell. Cette fonction fait le réel echo et vérifie ensuite si l' xoption a été activée lorsque l'alias a été appelé. Si l'option était activée, la fonction la rallume; s'il était désactivé, la fonction le laisse désactivé.

Vous pouvez insérer les sept lignes ci-dessus (huit, si vous incluez un shopt) au début du script et laisser le reste tranquille.

Cela vous permettrait

  1. d'utiliser l'une des lignes de shebang suivantes:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    ou tout simplement
    #! / bin / sh
    et cela devrait fonctionner comme prévu.
  2. avoir du code comme
    (shebang) 
    commande 1
     commande 2
     commande 3
    set -x
    commande 4
     commande 5
     commande 6
    set + x
    commande 7
     commande 8
     commande 9
    et
    • Les commandes 4, 5 et 6 seront tracées - sauf si l'une d'elles est un echo, auquel cas elle sera exécutée mais pas tracée. (Mais même si la commande 5 est un echo, la commande 6 sera toujours tracée.)
    • Les commandes 7, 8 et 9 ne seront pas tracées. Même si la commande 8 est un echo, la commande 9 ne sera toujours pas tracée.
    • Les commandes 1, 2 et 3 seront tracées (comme 4, 5 et 6) ou non (comme 7, 8 et 9) selon que le shebang inclut x.

PS J'ai découvert que, sur mon système, je peux laisser de côté le builtinmot - clé dans ma réponse du milieu (celui qui est juste un alias echo). Ce n'est pas surprenant; bash (1) dit que, pendant l'expansion des alias,…

… Un mot identique à un alias en cours de développement n'est pas développé une deuxième fois. Cela signifie que l' on peut alias lspour ls -F, par exemple, et bash ne cherche pas à développer récursive le texte de remplacement.

Sans surprise, la dernière réponse (celle avec echo_and_restore) échoue si le builtinmot-clé est omis 1 . Mais, curieusement, cela fonctionne si je supprime le builtinet change la commande:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Il semble donner lieu à un comportement indéfini. j'ai vu

  • une boucle infinie (probablement à cause d'une récursion illimitée),
  • un /dev/null: Bad addressmessage d'erreur, et
  • un core dump.

2
J'ai vu des tours de magie incroyables réalisés avec des alias, donc je sais que ma connaissance est incomplète. Si quelqu'un peut présenter un moyen de faire l'équivalent d' echo +x; echo "$*"; echo -xun alias, j'aimerais le voir.
G-Man dit `` Réintègre Monica ''

11

J'ai trouvé une solution partielle chez InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

les sorties

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Malheureusement, cela fait toujours écho set +x, mais au moins c'est calme après ça. c'est donc au moins une solution partielle au problème.

Mais existe-t-il peut-être une meilleure façon de procéder? :)


2

De cette façon, vous améliorez votre propre solution en vous débarrassant de la set +xsortie:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

2

Mettez set +xentre crochets, donc cela ne s'appliquerait qu'à la portée locale.

Par exemple:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

produirait:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true

Corrigez-moi si je me trompe, mais je ne pense pas que l' set +xintérieur du sous-shell (ou en utilisant des sous-coquilles du tout) fasse quelque chose d'utile. Vous pouvez le supprimer et obtenir le même résultat. C'est la redirection de stderr vers / dev / null qui fait le travail de "désactivation" temporaire du traçage ... Il semble echo foo1 2>/dev/null, etc., serait tout aussi efficace et plus lisible.
Tyler Rick

Le suivi dans votre script peut avoir un impact sur les performances. Deuxièmement, la redirection de & 2 vers NULL peut être différente, lorsque vous attendez d'autres erreurs.
kenorb

Corrigez-moi si je me trompe, mais dans votre exemple, le traçage est déjà activé dans votre script (avec bash -x) et vous redirigez déjà vers &2null (puisqu'il a &3été redirigé vers null), donc je ne sais pas en quoi ce commentaire est pertinent. Peut-être que nous avons juste besoin d'un meilleur exemple qui illustre votre point, mais dans l'exemple donné au moins, il semble toujours qu'il pourrait être simplifié sans perdre aucun avantage.
Tyler Rick

1

J'adore la réponse complète et bien expliquée de g-man , et je la considère comme la meilleure fournie jusqu'à présent. Il se soucie du contexte du script et ne force pas les configurations lorsqu'elles ne sont pas nécessaires. Donc, si vous lisez d'abord cette réponse, allez-y et vérifiez celle-ci, tout le mérite est là.

Cependant, dans cette réponse, il manque un élément important: la méthode proposée ne fonctionnera pas pour un cas d'utilisation typique, c'est-à-dire signaler des erreurs:

COMMAND || echo "Command failed!"

En raison de la façon dont l'alias est construit, cela s'étendra à

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

et vous l'avez deviné, echo_and_restores'exécute toujours , sans condition. Étant donné que la set +xpièce ne s'est pas exécutée, cela signifie que le contenu de cette fonction sera également imprimé.

Modification de la dernière ;à &&ne fonctionnerait pas non plus , parce que dans Bash, ||et &&sont associatifs gauche .

J'ai trouvé une modification qui fonctionne pour ce cas d'utilisation:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Il utilise un sous-shell (la (...)partie) afin de regrouper toutes les commandes, puis passe la chaîne d'entrée via stdin en tant que chaîne Here (la <<<chose) qui est ensuite imprimée par cat -. Le -est facultatif, mais vous savez, " explicite vaut mieux qu'implicite ".

Vous pouvez également utiliser cat -directement sans l' écho , mais j'aime cette façon car cela me permet d'ajouter d'autres chaînes à la sortie. Par exemple, j'ai tendance à l'utiliser comme ceci:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

Et maintenant, cela fonctionne à merveille:

false || echo "Command failed"
> [test.sh] Command failed

0

La trace d'exécution va à stderr, filtrez-la de cette façon:

./script.sh 2> >(grep -v "^+ echo " >&2)

Quelques explications, étape par étape:

  • stderr est redirigé ... - 2>
  • … À une commande. ->(…)
  • grep est la commande…
  • ... ce qui nécessite un début de ligne ... - ^
  • … À suivre par + echo
  • ... puis grepinverse le match ... --v
  • … Et cela élimine toutes les lignes dont vous ne voulez pas.
  • Le résultat irait normalement à stdout; nous le redirigeons là stderroù il appartient. ->&2

Le problème est (je suppose) que cette solution peut désynchroniser les flux. En raison du filtrage stderrpeut être un peu en retard par rapport à stdout(où la echosortie appartient par défaut). Pour y remédier, vous pouvez d'abord rejoindre les flux si cela ne vous dérange pas de les avoir tous les deux dans stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

Vous pouvez créer un tel filtrage dans le script lui-même, mais cette approche est certainement sujette à la désynchronisation (c'est-à-dire qu'elle s'est produite dans mes tests: la trace d'exécution d'une commande peut apparaître après la sortie de la suite echo).

Le code ressemble à ceci:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Exécutez-le sans aucune astuce:

./script.sh

Encore une fois, utilisez > >(grep -v "^+ echo ") 2>&1pour maintenir la synchronisation au prix de la jonction des flux.


Une autre approche. Vous obtenez une sortie "un peu redondante" et d'aspect étrange car votre terminal mélange stdoutetstderr . Ces deux courants sont des animaux différents pour une raison. Vérifiez si l'analyse stderrne correspond qu'à vos besoins; jeter stdout:

./script.sh > /dev/null

Si vous avez dans votre script un echomessage d'erreur de débogage / d'erreur, stderrvous pouvez vous débarrasser de la redondance de la manière décrite ci-dessus. Commande complète:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Cette fois, nous travaillons stderruniquement, la désynchronisation n'est donc plus un problème. Malheureusement, de cette façon, vous ne verrez ni trace ni sortie de echocette copie vers stdout(le cas échéant). Nous pourrions essayer de reconstruire notre filtre pour détecter la redirection ( >&2) mais si vous regardez echo foobar >&2, echo >&2 foobaret echo "foobar >&2"alors vous serez probablement d'accord pour dire que les choses se compliquent.

Cela dépend beaucoup des échos que vous avez dans vos scripts. Réfléchissez bien avant d'implémenter un filtre complexe, cela pourrait se retourner contre vous. Il vaut mieux avoir un peu de redondance que de manquer accidentellement des informations cruciales.


Au lieu de supprimer la trace d'exécution d'un, echonous pouvons supprimer sa sortie - et toute sortie à l'exception des traces. Pour analyser uniquement les traces d'exécution, essayez:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Infaillible? Pensez à ce qui se passera s'il y en a echo "+ rm -rf --no-preserve-root /" >&2dans le script. Quelqu'un pourrait avoir une crise cardiaque.


Et enfin…

Heureusement, il existe BASH_XTRACEFDune variable environnementale. De man bash:

BASH_XTRACEFD
S'il est défini sur un entier correspondant à un descripteur de fichier valide, bash écrira la sortie de trace générée lorsqu'il set -xest activé dans ce descripteur de fichier.

Nous pouvons l'utiliser comme ceci:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Notez que la première ligne génère un sous-shell. De cette façon, le descripteur de fichier ne restera pas valide ni la variable affectée par la suite dans le shell courant.

Grâce à BASH_XTRACEFDvous, vous pouvez analyser les traces sans échos et autres sorties, quelles qu'elles soient. Ce n'est pas exactement ce que vous vouliez mais mon analyse me fait penser que c'est (en général) la bonne façon.

Bien sûr, vous pouvez utiliser une autre méthode, surtout lorsque vous devez analyser stdoutet / ou stderraccompagner vos traces. Vous avez juste besoin de vous rappeler qu'il y a certaines limites et pièges. J'ai essayé de les montrer (certains).


0

Dans un Makefile, vous pouvez utiliser le @symbole.

Exemple d'utilisation: @echo 'message'

De ce document GNU:

Lorsqu'une ligne commence par '@', l'écho de cette ligne est supprimé. Le '@' est rejeté avant que la ligne ne soit passée au shell. En règle générale, vous utiliseriez ceci pour une commande dont le seul effet est d'imprimer quelque chose, comme une commande d'écho pour indiquer la progression dans le makefile.


Salut, le document auquel vous faites référence est pour gnu make, pas pour shell. Êtes-vous sûr que cela fonctionne? Je reçois l'erreur ./test.sh: line 1: @echo: command not found, mais j'utilise bash.

Wow, désolé d'avoir mal lu la question. Oui, @ne fonctionne que lorsque vous faites écho dans les makefiles.
derek

@derek J'ai modifié votre réponse d'origine afin qu'elle indique désormais clairement que la solution est limitée aux Makefiles uniquement. En fait, je cherchais celui-ci, donc je voulais que votre commentaire n'ait pas une réputation négative. Espérons que les gens le trouveront utile aussi.
Shakaron

-1

Modifié le 29 octobre 2016 par le modérateur, suggère que l'original ne contenait pas suffisamment d'informations pour comprendre ce qui se passe.

Cette "astuce" produit une seule ligne de sortie de message sur le terminal lorsque xtrace est actif:

La question d'origine est Y a - t-il un moyen de laisser -x activé, mais uniquement de générer un message au lieu des deux lignes . Ceci est une citation exacte de la question.

Je comprends que la question soit, comment "laisser set -x activé ET produire une seule ligne pour un message"?

  • Dans le sens global, cette question concerne essentiellement l'esthétique - le questionneur veut produire une seule ligne au lieu des deux lignes pratiquement dupliquées produites pendant que xtrace est actif.

Donc en résumé, le PO nécessite:

  1. Pour avoir ensemble -x en vigueur
  2. Produire un message lisible par l'homme
  3. Produisez une seule ligne de sortie de message.

L'OP n'exige pas que la commande echo soit utilisée. Ils l'ont cité comme un exemple de production de messages, en utilisant l'abréviation par exemple qui signifie latin exempli gratia, ou "par exemple".

En pensant "hors des sentiers battus" et en abandonnant l'utilisation de l' écho pour produire des messages, je note qu'une déclaration d'affectation peut répondre à toutes les exigences.

Effectuez une affectation d'une chaîne de texte (contenant le message) à une variable inactive.

L'ajout de cette ligne à un script ne modifie aucune logique, mais produit une seule ligne lorsque le traçage est actif: (Si vous utilisez la variable $ echo , changez simplement le nom en une autre variable inutilisée)

echo="====================== Divider line ================="

Ce que vous verrez sur le terminal est une seule ligne:

++ echo='====================== Divider line ================='

pas deux, car l'OP n'aime pas:

+ echo '====================== Divider line ================='
====================== Divider line =================

Voici un exemple de script à démontrer. Notez que la substitution de variables dans un message (le nom du répertoire $ HOME à 4 lignes de la fin) fonctionne, vous pouvez donc tracer des variables à l'aide de cette méthode.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

Et voici la sortie de l'exécuter à la racine, puis le répertoire personnel.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

1
Notez que même la version modifiée de votre réponse supprimée (dont il s'agit d'une copie) ne répond toujours pas à la question, car la réponse ne génère pas uniquement le message sans la commande d'écho associée, ce que l'OP a demandé.
DavidPostill

Remarque, cette réponse est en cours de discussion sur la méta. Ai-je été pénalisé pour une réponse 1 modérateur n'a pas aimé?
DavidPostill

@David J'ai agrandi l'explication, par les commentaires du meta forum.
HiTechHiTouch

@David Encore une fois, je vous encourage à lire attentivement le PO, en faisant attention au latin ".eg". L'affiche ne nécessite PAS l'utilisation de la commande echo. L'OP fait uniquement référence à l'écho comme exemple (avec redirection) de méthodes potentielles pour résoudre le problème. Il s'avère que vous n'avez pas besoin non plus,
HiTechHiTouch

Et si le PO veut faire quelque chose comme ça echo "Here are the files in this directory:" *?
G-Man dit `` Réintègre Monica ''
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.