À quoi sert la variable $ BASH_COMMAND?

25

Selon le manuel de Bash , la variable d'environnement BASH_COMMANDcontient

La commande en cours d'exécution ou sur le point d'être exécutée, sauf si le shell exécute une commande à la suite d'une interruption, auquel cas il s'agit de la commande en cours d'exécution au moment de l'interruption.

En prenant ce cas de coin de piège de côté, si je comprends bien, cela signifie que lorsque j'exécute une commande, la variable BASH_COMMANDcontient cette commande. Il n'est pas absolument clair si cette variable n'est pas définie après l'exécution de la commande (c'est-à-dire qu'elle n'est disponible que lorsque la commande est en cours d'exécution, mais pas après), bien que l'on puisse dire que puisque c'est "la commande en cours d'exécution ou sur le point d'être exécutée" , ce n'est pas la commande qui vient d'être exécutée.

Mais vérifions:

$ set | grep BASH_COMMAND=
$ 

Vide. Je m'attendais à voir BASH_COMMAND='set | grep BASH_COMMAND='ou peut-être juste BASH_COMMAND='set', mais le vide m'a surpris.

Essayons autre chose:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Eh bien, cela a du sens. J'exécute la commande echo $BASH_COMMANDet donc la variable BASH_COMMANDcontient la chaîne echo $BASH_COMMAND. Pourquoi cela a-t-il fonctionné cette fois, mais pas avant?

Faisons à setnouveau la chose:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Alors attend. Il a été défini lorsque j'ai exécuté cette echocommande, et il n'a pas été annulé par la suite. Mais lorsque j'ai exécuté à setnouveau, la commande BASH_COMMAND n'a pas été définie set. Quelle que soit la fréquence d'exécution de la setcommande ici, le résultat reste le même. Donc, la variable est-elle définie lors de l'exécution echo, mais pas lors de l'exécution set? Voyons voir.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Quelle? La variable a donc été définie lors de mon exécution echo $BASH_COMMAND, mais pas lors de mon exécution echo Hello AskUbuntu? Où est la différence maintenant? La variable n'est-elle définie que lorsque la commande actuelle elle-même force réellement le shell à évaluer la variable? Essayons quelque chose de différent. Peut-être une commande externe cette fois, pas une commande bash intégrée, pour un changement.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Hmm, ok ... encore une fois, la variable a été définie. Alors, ma supposition actuelle est-elle correcte? La variable n'est-elle définie que lorsqu'elle doit être évaluée? Pourquoi? Pourquoi? Pour des raisons de performances? Faisons encore un essai. Nous allons essayer de grep pour $BASH_COMMANDdans un fichier, et puisque $BASH_COMMANDdevrait alors contenir une grepcommande, grepdevrait grep pour cette grepcommande (c'est-à-dire pour lui-même). faisons donc un fichier approprié:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ok, intéressant. La commande a grep $BASH_COMMAND tmpété étendue à grep grep $BASH_COMMAND tmp tmp(la variable n'est développée qu'une seule fois, bien sûr), et j'ai donc recherché grepune fois dans un fichier $BASH_COMMANDqui n'existe pas et deux fois dans le fichier tmp.

Q1: Mon hypothèse actuelle est-elle correcte:

  • BASH_COMMANDn'est définie que lorsqu'une commande essaie de l'évaluer; et
  • il n'est pas instable après exécution d'une commande, même si la description peut nous le faire croire?

Q2: Si oui, pourquoi? Performance? Si non, comment expliquer autrement le comportement dans la séquence de commandes ci-dessus?

Q3: Enfin, existe-t-il un scénario dans lequel cette variable pourrait réellement être utilisée de manière significative? J'essayais en fait de l'utiliser à l'intérieur $PROMPT_COMMANDpour analyser la commande en cours d'exécution (et faire des choses en fonction de cela), mais je ne peux pas, car dès que, dans mon $PROMPT_COMMAND, j'exécute une commande pour regarder la variable $BASH_COMMAND, la variable obtient des ensembles à cette commande. Même lorsque je fais MYVARIABLE=$BASH_COMMANDjuste au début de mon $PROMPT_COMMAND, MYVARIABLEcontient alors la chaîne MYVARIABLE=$BASH_COMMAND, car une affectation est aussi une commande. (Cette question ne porte pas sur la façon dont je pourrais obtenir la commande actuelle dans une $PROMPT_COMMANDexécution. Il existe d'autres moyens, je sais.)

C'est un peu comme avec le principe d'incertitude de Heisenberg. Juste en observant la variable, je la change.

Malte Skoruppa
la source
5
Sympa, mais probablement mieux adapté à unix.stackexchange.com ... il y a de vrais bashüber-gourous là-bas.
Rmano
Eh bien, est-il possible de le déplacer là-bas? Mods?
Malte Skoruppa
Je ne sais vraiment pas comment le faire --- je suppose que c'est un privilège Mod. D'un autre côté, je ne pense pas que le signalement soit judicieux --- voyons si quelqu'un agit sur le drapeau de fermeture.
Rmano
1
Pour le troisième point, c'est ce que vous recherchez. Peut-être que sur la base de cet article, vous pouvez également trouver la réponse aux deux premiers points.
Radu Rădeanu
2
+1 m'a dérouté, mais c'était amusant à lire!
Joe

Réponses:

15

Répondre à la troisième question: bien sûr, il peut être utilisé de manière significative dans la façon dont le manuel Bash laisse clairement entendre - dans un piège, par exemple:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
fgfdjsa failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
cat /etc/fgfdjsa failed with error code 1
Dmitry Alexandrov
la source
Ah, bien sûr. Alors diriez-vous qu'un piège est le seul contexte dans lequel cette variable a un sens? Cela ressemblait à un cas d'angle dans le manuel: "La commande en cours d'exécution (...), à moins que le shell n'exécute une commande à la suite d'un piège, ..."
Malte Skoruppa
Après avoir lu les articles liés dans la réponse de @DocSalvager, il est clair que cela $BASH_COMMANDpeut en effet être utilisé de manière significative même dans des contextes autres qu'un piège. Pourtant, l'utilisation que vous décrivez est probablement la plus typique et la plus simple. Donc, votre réponse, et celle de DocSalvager, répond le mieux à mon Q3 , et c'était la plus importante pour moi. En ce qui concerne Q1 et Q2, comme indiqué par @zwets, ces choses ne sont pas définies. Il se peut qu'on $BASH_COMMANDne donne une valeur que si vous y accédez, pour des performances - ou bien des conditions de programme internes étranges peuvent conduire à ce comportement. Qui sait?
Malte Skoruppa,
1
Sur un sidenote, j'ai découvert que vous pouvez forcer $BASH_COMMANDà être toujours défini, en forçant l'évaluation d' $BASH_COMMANDavant chaque commande. Pour ce faire, nous pouvons utiliser le piège DEBUG, qui est exécuté avant chaque commande unique (tous em). Si nous émettons, par exemple, trap 'echo "$BASH_COMMAND" > /dev/null' DEBUGalors $BASH_COMMANDsera toujours évalué avant chaque commande (et assigné la valeur de cela $COMMAND). Donc, si j'exécute set | grep BASH_COMMAND=alors que ce piège est actif ... que savez-vous? Maintenant, la sortie est BASH_COMMAND=set, comme prévu :)
Malte Skoruppa
6

Maintenant que Q3 a été répondu (correctement, à mon avis: BASH_COMMANDest utile dans les pièges et presque nulle part ailleurs), essayons Q1 et Q2.

La réponse à Q1 est: l'exactitude de votre hypothèse est indécidable. La vérité de aucun des points ne peut être établie, car ils posent des questions sur un comportement non spécifié. Par sa spécification, la valeur de BASH_COMMANDest définie sur le texte d'une commande pendant la durée de l'exécution de cette commande. La spécification n'indique pas sa valeur dans toute autre situation, c'est-à-dire lorsqu'aucune commande n'est en cours d'exécution. Il pourrait avoir n'importe quelle valeur ou aucune.

La réponse à Q2 "Si non, comment expliquer autrement le comportement dans la séquence de commandes ci-dessus?" suit alors logiquement (si quelque peu pédantiquement): il s'explique par le fait que la valeur de BASH_COMMANDn'est pas définie. Comme sa valeur n'est pas définie, elle peut avoir n'importe quelle valeur, ce qui est exactement ce que la séquence montre.

Postscript

Il y a un point où je pense que vous avez effectivement un faible dans la spécification. C'est là que vous dites:

Même lorsque je fais MYVARIABLE = $ BASH_COMMAND au début de mon $ PROMPT_COMMAND, alors MYVARIABLE contient la chaîne MYVARIABLE = $ BASH_COMMAND, car une affectation est également une commande .

La façon dont je lis la page de manuel bash, le bit en italique n'est pas vrai. La section SIMPLE COMMAND EXPANSIONexplique comment les affectations de variables sur la ligne de commande sont d'abord mises de côté, puis

Si aucun nom de commande ne résulte [en d'autres termes, il n'y avait que des affectations de variables], les affectations de variables affectent l'environnement shell actuel.

Cela me suggère que les affectations de variables ne sont pas des commandes (et donc exemptées de s'afficher dans BASH_COMMAND), comme dans d'autres langages de programmation. Cela expliquerait également pourquoi il n'y a pas de ligne BASH_COMMAND=setdans la sortie de set, setétant essentiellement un «marqueur» syntaxique pour l'affectation des variables.

OTOH, dans le dernier paragraphe de cette section, il est dit

S'il reste un nom de commande après l'expansion, l'exécution se déroule comme décrit ci-dessous. Sinon, la commande se ferme.

... ce qui suggère le contraire, et les affectations de variables sont également des commandes.

zwets
la source
1
Ce n'est pas parce qu'une hypothèse ne peut pas être établie qu'elle est incorrecte. Einstein a supposé que la vitesse de la lumière est la même pour tout observateur (à n'importe quelle vitesse) qu'une hypothèse fondamentale pour la théorie de la relativité; cela ne peut pas être établi, mais cela ne signifie pas qu'il est incorrect. Les propriétés "peuvent être établies" et "est correct" sont orthogonales. Au mieux, vous pourriez dire "Nous ne savons pas si c'est correct, car cela ne peut pas être établi". De plus, il est spécifié: c'est la commande courante pendant l'exécution. Donc, si je tape, setje devrais voir BASH_COMMAND='set'quelque part dans cette sortie, qui
Malte Skoruppa
... mais ce n'est pas le cas. Même si c'est lors de l'exécution de la setcommande . Par conséquent, selon les spécifications, cela devrait fonctionner. Est-ce donc que l'implémentation n'obéit pas fidèlement aux spécifications, ou est-ce que je comprends mal les spécifications? Trouver la réponse à cette question est en quelque sorte l'objectif du premier trimestre. +1 pour votre postscript :)
Malte Skoruppa
@MalteSkoruppa Bons points. Correction de la réponse en conséquence et ajout d'une remarque sur set.
zwets
1
Il est possible que dans l'interpréteur bash, ce setne soit pas une "commande". Tout comme ce =n'est pas une "commande". Le traitement du texte suivant / entourant ces jetons pourrait avoir une logique entièrement différente du traitement d'une "commande".
DocSalvager
Bons points de vous deux. :) Il se pourrait bien que setcela ne compte pas comme une "commande". Je n'aurais pas pensé que $BASH_COMMANDcela se révélerait si peu spécifié dans de nombreuses circonstances. Je suppose que nous avons au moins établi que la page de manuel pourrait être plus claire à ce sujet;)
Malte Skoruppa
2

Une utilisation inventive de $ BASH_COMMAND

Récemment trouvé cette utilisation impressionnante de $ BASH_COMMAND dans l'implémentation d'une fonctionnalité de type macro.

C'est l'astuce de base de l'alias et remplace l'utilisation du piège DEBUG. Si vous lisez la partie dans le post précédent sur le piège DEBUG, vous reconnaîtrez la variable $ BASH_COMMAND. Dans ce post, j'ai dit qu'il était réglé sur le texte de la commande avant chaque appel au piège DEBUG. Eh bien, il s'avère qu'il est défini avant l'exécution de chaque commande, piège DEBUG ou non (par exemple, exécutez 'echo "cette commande = $ BASH_COMMAND"' pour voir de quoi je parle). En lui affectant une variable (juste pour cette ligne), nous capturons BASH_COMMAND à la portée la plus externe de la commande, qui contiendra la commande entière.

L' article précédent de l'auteur fournit également un bon contexte lors de la mise en œuvre d'une technique utilisant un DEBUG trap. Le trapest éliminé dans la version améliorée.

DocSalvager
la source
+1 J'ai finalement pu me déplacer pour lire ces deux articles. Hou la la! Quelle lecture! Très instructif. Ce mec est un gourou bash ! Gloire! Cela répond également à mon commentaire sur la réponse de Dmitry sur la question de savoir si elle $BASH_COMMANDpeut être utilisée de manière significative dans un contexte autre que a trap. Et quelle utilité! L'utiliser pour implémenter des macro-commandes dans bash - qui l'aurait cru possible? Merci pour le pointeur.
Malte Skoruppa,
0

Une utilisation apparemment courante du piège ... le débogage consiste à améliorer les titres qui apparaissent dans la liste des fenêtres (^ A ") lorsque vous utilisez" screen ".

J'essayais de rendre "l'écran", "la liste des fenêtres" plus utilisable alors j'ai commencé à trouver des articles faisant référence à "trap ... debug".

J'ai trouvé que la méthode utilisant PROMPT_COMMAND pour envoyer la "séquence de titres nulle" ne fonctionnait pas si bien, donc je suis revenu à la méthode trap ... debug.

Voici comment procéder:

1 Dites à "screen" de rechercher la séquence d'échappement (activez-la) en mettant "shelltitle '$ | bash:'" dans "$ HOME / .screenrc".

2 Désactivez la propagation du piège de débogage en sous-shells. Ceci est important, car s'il est activé, tout est bloqué, utilisez: "set + o functrace".

3 Envoyez la séquence d'échappement du titre pour "screen" à interpréter: trap 'printf "\ ek $ (date +% Y% m% d% H% M% S) $ (whoami) @ $ (hostname): $ (pwd) $ {BASH_COMMAND} \ e \ "'" DEBUG "

Ce n'est pas parfait, mais cela aide, et vous pouvez utiliser cette méthode pour mettre littéralement tout ce que vous aimez dans le titre

Voir la "liste des fenêtres" suivante (5 écrans):

Indicateurs de nom Num

1 bash: 20161115232035 mcb @ ken007: / home / mcb / ken007 RSYNCCMD = "sudo rsync" myrsync.sh -R -r "$ {1}" "$ {BACKUPDIR} $ {2: +" / $ {2} " } "$ 2 bash: 20161115230434 mcb @ ken007: / home / mcb / ken007 ls --color = auto -la $ 3 bash: 20161115230504 mcb @ ken007: / home / mcb / ken007 poubelle / psg.sh 4 $ bash: 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 5 $ bash: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

Malcolm Boekhoff
la source