web-dev-qa-db-fra.com

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

J'utilise des scripts Shell de Jenkins, qui lancent des scripts Shell avec les options Shebang #!/bin/sh -ex.

Selon Bash Shebang pour les nuls? , -x, "permet au Shell d’imprimer une trace d’exécution", ce qui est très bien dans la plupart des cas - sauf pour les échos:

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 -x activé, mais uniquement en sortie

Message

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

25
Christian

Quand vous êtes au cou des alligators, il est facile d’oublier que l’objectif était de drainer le marais.- dicton populaire

La question concerne echo, et pourtant, la majorité des réponses jusqu’à présent se sont concentrées sur la manière d’insérer une commande set +x. 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 fastidieux, mais si vous avez un bloc de commandes echo consécutives, vous n’avez pas besoin de le faire 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 nouvelles lignes.

Vous pouvez réduire la charge de frappe en utilisant l'idée de kenorb d'ouvrir /dev/null de manière permanente sur un descripteur de fichier non standard (par exemple, 3), puis en disant 2>&3 au lieu de 2> /dev/null tout 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, fastidieux) chaque fois que vous faites une echo. Si vous voulez vraiment que toutes echo commandes suppriment la trace d’exécution (et pourquoi pas vous?), Vous pouvez le faire globalement sans perdre beaucoup de code. Tout d’abord, j’ai remarqué que des pseudonymes n’étaient pas retrouvé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 Shebang est #!/bin/sh, même si /bin/sh est un lien vers bash. Toutefois, si Shebang est #!/bin/bash, vous devez ajouter une commande shopt -s expand_aliases pour que les alias fonctionnent dans un script.)

Donc, pour mon premier tour:

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

Désormais, lorsque 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 commande set (à l'aide de la technique présentée en premier dans de la réponse de user5071535 ), puis réelle echo commande. Cela nous permet d’obtenir un effet similaire à celui de la réponse de user5071535 sans avoir à modifier le code à chaque commande echo. Cependant, cela laisse le mode trace désactivé. Nous ne pouvons pas mettre un set -x dans l'alias (ou du moins pas facilement) car un alias permet uniquement de remplacer une chaîne par 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 de trace après avoir affiché le (s) message (s) - mais une seule fois après chaque bloc de commandes echo consécutives:

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


Ce serait bien si nous pouvions rendre le set -x automatique après une echo - et nous le pourrions, avec un peu plus de ruse. Mais avant de présenter cela, réfléchissez à ceci. Le PO commence avec des scripts qui utilisent un #!/bin/sh -ex Shebang. Implicitement, l'utilisateur pourrait supprimer la x du Shebang et disposer d'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 activent le suivi après les déclarations echo, sans condition, sans se soucier de savoir si elles étaient déjà actives ou non. Cette réponse ne reconnaît pas ce problème de manière évidente, car elle remplace echo sortie par une sortie de trace; par conséquent, tous les messages disparaissent si le traçage est désactivé. Je vais maintenant présenter une solution qui réactive le suivi après une instruction echo conditionnellement - uniquement si elle était déjà activée. Le déclassement de cette solution en une solution qui rend le traçage "retour" inconditionnel est trivial et reste un 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 e et x sont définies, alors $- sera un fouillis de lettres comprenant e et x. Mon nouvel alias (ci-dessus) enregistre la valeur $- avant de désactiver le traçage. Ensuite, lorsque le traçage est désactivé, le contrôle est transféré dans une fonction Shell. Cette fonction effectue la echo réelle et vérifie ensuite si l'option x a été activée lorsque l'alias a été appelé. Si l'option était activée, la fonction la rallume; si elle était désactivée, la fonction la laisse désactivée.

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

Cela vous permettrait

  1. d'utiliser l'une des lignes Shebang suivantes:
    #!/bin/sh -ex 
     #!/bin/sh -e 
     #!/bin/sh –x
    ou simplement
    #!/bin/sh
    et cela devrait fonctionner comme prévu.
  2. avoir un code comme
    (Case)commander1commander2commander3
     set -x 
    commander4commander5commander6
     set + x 
    commander7commander8commander9
    et
    • Les commandes 4, 5 et 6 seront suivies - à moins que l’une d’elles ne soit une echo, auquel cas elle sera exécutée mais non tracée. (Mais même si la commande 5 est une 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 une 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 comprend ou non x.

P.S. J'ai découvert que, sur mon système, je pouvais omettre le mot clé builtin dans la réponse du milieu (celle qui est simplement un alias pour echo). Ce n'est pas surprenant. bash (1) dit que, pendant l'expansion du pseudonyme,…

… Un mot identique à un alias en cours d'expansion n'est pas développé une seconde fois. Cela signifie que l'on peut alias ls à ls -F, par exemple, et que bash n'essaie pas de développer de manière récursive le texte de remplacement.

Sans surprise, la dernière réponse (celle avec echo_and_restore) échoue si le mot clé builtin est omis.1. Mais curieusement, cela fonctionne si je supprime la builtin et change l’ordre:

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

__________
1Cela semble donner lieu à un comportement indéfini. J'ai vu

  • une boucle infinie (probablement à cause d'une récursion sans bornes),
  • un message d'erreur /dev/null: Bad address, et
  • un dépotoir.
16
G-Man

J'ai trouvé une solution partielle sur 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 cela. c'est donc au moins une solution partielle au problème.

Mais y a-t-il peut-être une meilleure façon de faire cela? :)

11
Christian

Cela améliore votre propre solution en éliminant la sortie de set +x:

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

Mettez set +x entre crochets, cela ne s'appliquerait donc que pour 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
2
kenorb

J'adore la réponse complète et bien expliquée de g-man et considère que c'est la meilleure solution fournie à ce jour. Il se soucie du contexte du script et ne force pas les configurations lorsqu'elles ne sont pas nécessaires. Donc, si vous lisez cette réponse, allez-y d’abord et vérifiez-la, 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, à savoir les erreurs de rapport:

COMMAND || echo "Command failed!"

En raison de la façon dont l'alias est construit, celui-ci sera étendu

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

et vous l'avez deviné, echo_and_restore est exécuté toujours , sans condition. Étant donné que la partie set +x n'a pas été exécutée, cela signifie que le contenu de cette fonction sera également imprimé.

Changer le dernier ; en && ne fonctionnerait pas non plus, car dans Bash, || et && sont à gauche-associative .

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 (...)) pour regrouper toutes les commandes, puis transmet la chaîne d'entrée à travers stdin en tant que Here String (le <<< chose) qui est ensuite imprimé par cat -. Le - est facultatif, mais vous savez que " explicite vaut mieux que implicite ".

Vous pouvez aussi utiliser cat - directement sans le echo , mais j'aime bien 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 ça marche à merveille:

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

La trace d'exécution va dans stderr, filtrez-la comme suit:

./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 de + echo
  • … Alors grep inverse la correspondance… - -v
  • … Et qui supprime toutes les lignes que vous ne voulez pas.
  • Le résultat devrait normalement aller à stdout; nous le redirigeons vers stderr où il appartient. - >&2

Le problème est (je suppose) que cette solution peut désynchroniser les flux. En raison du filtrage, stderr peut être un peu tardif par rapport à stdout (auquel appartient la sortie echo par défaut). Pour résoudre ce problème, 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 construire un tel filtrage dans le script lui-même mais cette approche est sujette à la désynchronisation à coup sûr (c'est-à-dire que cela s'est produit dans mes tests: trace d'exécution d'un Cette commande peut apparaître après le résultat de la suite de echo).

Le code ressemble à ceci:

#!/bin/bash -x

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

Exécutez-le sans astuces:

./script.sh

De nouveau, utilisez > >(grep -v "^+ echo ") 2>&1 pour maintenir la synchronisation au détriment de la jonction des flux.


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

./script.sh > /dev/null

Si vous avez dans votre script un message echo imprimant un message de débogage/d'erreur à stderr, vous 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 avec stderr uniquement, la désynchronisation n'est donc plus un problème. Malheureusement, de cette façon, vous ne verrez ni trace ni sortie de echo imprimant sur 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 foobar et echo "foobar >&2", vous conviendrez probablement 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, il pourrait se retourner contre vous. Il vaut mieux avoir un peu de redondance que de rater accidentellement des informations cruciales.


Au lieu de supprimer la trace d'exécution d'une echo, nous pouvons en supprimer la sortie - et toutes les sorties sauf les traces. Pour analyser uniquement les traces d'exécution, essayez:

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

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


Et enfin…

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

BASH_XTRACEFD
Si la valeur est un entier correspondant à un descripteur de fichier valide, bash écrit le résultat de la trace généré lorsque set -x est activé sur 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, pas plus que la variable affectée dans le Shell en cours.

Grâce à BASH_XTRACEFD, vous pouvez analyser des traces libres d’échos et d’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, en particulier lorsque vous devez analyser stdout et/ou stderr avec vos traces. Vous devez simplement vous rappeler qu'il existe certaines limitations et certains pièges. J'ai essayé de les montrer.

0
Kamil Maciorowski

Dans un Makefile, vous pouvez utiliser le symbole @.

Exemple d'utilisation: @echo 'message'

De ceci GNU doc:

Quand une ligne commence par "@", l’écho de cette ligne est supprimé. Le "@" est supprimé avant que la ligne ne soit transmise au shell. En règle générale, vous utiliseriez ceci pour une commande dont le seul effet est d'imprimer quelque chose, telle qu'une commande echo pour indiquer la progression dans le fichier makefile.

0
derek