Quel est le dernier argument de la commande précédente?

12

$_ est dit être le dernier argument de la commande précédente.

Je me demande donc pourquoi ce n'est pas le cas EDITOR="emacs -nw"mais EDITORdans l'exemple suivant?

Pourquoi ne fait-il pas "emacs -nw"partie du dernier argument?

Plus généralement, quelles sont les définitions d'un argument, et le dernier argument?

Merci.

$ export EDITOR="emacs -nw"
$ echo $_
EDITOR
Tim
la source
3
Je pense que c'est pour la même raison que shellcheck vous dit de ne pas exporter les variables sur la même ligne que vous les affectez. L'affectation se produit, puis la variable est exportée. EDITORest un argument pour exporter
jesse_b
FWIW, pdkshet dashinclura la valeur qui a été affectée, mais ksh93se comportera de la bashmême manière.
Kusalananda
zsh:, export FOO=bar; echo $_imprime export.
ilkkachu
@Jesse_b Le tout est le dernier argument / opérande (y compris la valeur attribuée), mais cela peut avoir quelque chose à voir avec le fait qu'il exports'agit d'un utilitaire intégré.
Kusalananda
ksh: imprime typeset -x FOO=barensuite , mais dans Bash imprime . echo $_FOOdeclare -x FOO=bar; echo $_FOO=bar
ilkkachu

Réponses:

13

Bash traite les affectations de variables, quand ils sont autorisés comme arguments (avec alias, declare, export, local, readonly, et typeset), avant toute autre chose (ou plutôt, il les identifie avant tout - l' expansion s'applique aux valeurs affectées aux variables). En ce qui concerne l'expansion des mots, la commande restante est export EDITOR, donc _est définie sur EDITOR.

De manière générale, les arguments sont les «mots» restants après l'expansion (qui n'incluent pas les affectations de variables et les redirections).

Voir Extension de commande simple dans le manuel de Bash pour plus de détails.

Stephen Kitt
la source
Et je réalise que declarele comportement de ne correspond pas à ce que je décris ...
Stephen Kitt
Eh bien, ce n'est pas très cohérent. declare a=b; echo $_gravures a=b; export c=d; echo $_imprime juste c. aliassemble imprimer seulement le nom, locald'un autre côté, imprime tout l'argument. Et readonlyimprime également juste le nom, ce que j'ai trouvé un peu surprenant car j'aurais pensé readonlyet localserait similaire declare.
ilkkachu du
1
@ilkkachu heh, je m'en suis rendu compte aussi (voir ci-dessus). exportet readonlysont déclarés ensemble setattr.def, declare, localet typesetsont déclarés dans declare.def, aliasest le seul dans alias.def.
Stephen Kitt
Merci. Lorsque les affectations de variables sont utilisées comme arguments pour certaines commandes, (1) "(ou plutôt, il les identifie avant toute autre chose - l'expansion s'applique aux valeurs affectées aux variables)", voulez-vous dire que l'expansion se produit pour les valeurs avant d'effectuer l'affectation des variables? (2) "Quand il s'agit de l'expansion des mots, la commande restante est l'exportation EDITOR", voulez-vous dire que l'affectation des variables se produit avant l'expansion? Les deux citations semblent se contredire.
Tim
Merci. Je suis un peu confus. Lorsque les affectations de variables sont utilisées comme arguments pour alias, declare, export, local, readonlyet typeset. Que se passe-t-il en premier et ensuite? "Quand il s'agit de l'expansion des mots, la commande restante est export EDITOR", impliquez-vous que l'affectation des variables EDITOR="emacs -nw"se produit avant l'expansion? Sinon, pourquoi la commande restante ne contient pas l'affectation comme argument? Si oui, l'expansion sur les valeurs affectées aux variables ne doit-elle pas se produire avant d'effectuer l'affectation des variables?
Tim
4

TL; DR: dans le cas de export FOO=bar, bash invoque sa création d'environnement temporaire, définit FOO=bardans cet environnement, puis donne une commande finale de export FOO. À ce stade, FOOest pris comme dernier argument.


Ah, les abusés $_:

($ _, un trait de soulignement.) Au démarrage du shell, définissez le chemin d'accès absolu utilisé pour appeler le shell ou le script de shell en cours d'exécution tel que transmis dans l'environnement ou la liste d'arguments. Par la suite, se développe jusqu'au dernier argument de la commande précédente, après expansion. Définissez également le chemin d'accès complet utilisé pour appeler chaque commande exécutée et placée dans l'environnement exporté vers cette commande. Lors de la vérification du courrier, ce paramètre contient le nom du fichier courrier.

Regardons quelques variantes:

$ man; echo $_
What manual page do you want?
man
$ man foo; echo $_
No manual entry for foo
foo
$ echo; echo $_

echo
$ echo bar foo; echo $_
bar foo
foo
$ foo=x eval 'echo $foo'; echo $_
x
echo $foo
$ bar() { man $1; }; echo $_
foo
$ for (( i=0; $i<0; i=i+1 )); do echo $i; done; echo $_
foo
$ bar; echo $_
What manual page do you want?
man
$ bar foo; echo $_
No manual entry for foo
foo
$ MANPATH=/tmp; echo $_

$ export MANPATH=/tmp; echo $_
MANPATH

Nous voyons donc trois modèles ici:

  • Les commandes invoquées depuis le système de fichiers, les fonctions et les commandes intégrées se comportent comme prévu: $_est défini sur le nom de la commande lui-même si aucun argument, sinon le dernier des arguments présentés.
  • Après définitions de fonctions, boucles et autres constructions logiques: $_n'est pas modifié.
  • Tout le reste: $_est réglé sur quelque chose qui n'est pas tout à fait attendu; bizarre.

J'ai instrumenté le code pour donner un aperçu de l'étrangeté.

$ ./bash --noprofile --norc -c 'man foo'
lastword=[man]
lastarg=[foo]
$ ./bash --noprofile --norc -c 'export FOO=bar'
lastword=[export]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[bar]
before bind_lastarg, lastarg=[FOO]
bind_lastarg, arg=[FOO]
bind_variable, name=[_], value=[FOO]
$ ./bash --noprofile --norc -c 'declare FOO=bar'
lastword=[declare]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[(null)]
before bind_lastarg, lastarg=[FOO=bar]
bind_lastarg, arg=[FOO=bar]
bind_variable, name=[_], value=[FOO=bar]

Vous pouvez voir que l' analyseur voit le dernier argument attendu ( lastarg=) dans tous les cas, mais ce qui se passe ensuite dépend de ce que bash pense que cela devrait se produire. Voir execute_cmd.c, execute_simple_command () .

Dans le cas de export FOO=bar, bash effectue l'affectation puis exporte la variable. Cela semble cohérent avec l'affirmation de la documentation selon laquelle le dernier argument a été calculé après l'expansion.

évêque
la source
1
Comment le shell sait-il que vous vérifiez le courrier?
rackandboneman
@rackandboneman sans confirmer, je soupçonne les contrôles internes effectués sur la base deMAILCHECK
Jeff Schaller
2

Pour répondre à la question du titre, essayez !$:

$ export EDITOR="emacs -nw"
$ echo !$
EDITOR=emacs -nw

Il s'agit d'une expansion historique. Depuis la page de manuel bash:

L'expansion de l'historique est effectuée immédiatement après la lecture d'une ligne complète, avant que le shell ne la décompose en mots. Elle se déroule en deux parties. La première consiste à déterminer la ligne de la liste d'historique à utiliser lors de la substitution. La seconde consiste à sélectionner des parties de cette ligne à inclure dans la ligne actuelle. La ligne sélectionnée dans l'historique est l'événement, et les parties de cette ligne qui sont traitées sont des mots.

...

Designateurs d'événements

...

! Démarrez une substitution d'historique, sauf lorsqu'il est suivi d'un blanc, d'un retour à la ligne, d'un retour chariot, = ou ((lorsque l'option shell extglob est activée à l'aide de la fonction interne shopt).

...

!! Reportez-vous à la commande précédente. Il s'agit d'un synonyme de «! -1».

...

Désignateurs de mots

...

$ Le dernier mot. Il s'agit généralement du dernier argument, mais sera étendu au mot zéro s'il n'y a qu'un seul mot dans la ligne.

...

Si un désignateur de mot est fourni sans spécification d'événement, la commande précédente est utilisée comme événement.

JoL
la source
Vous prenez le titre de la question waaaaay trop littéralement. (OK, c'est un titre médiocre.) Nous pouvons tous voir que la commande « export EDITOR="emacs -nw"» se compose de deux mots: le premier est « export» et le second est « EDITOR="emacs -nw"». La question est vraiment de demander: «Que signifient la page de manuel bash et le manuel Bash quand ils disent que !_« se développe jusqu'au dernier argument de la commande précédente », étant donné que bash est défini $_sur« EDITOR»dans ce cas?» Copier et coller la section de page de manuel bash sur l'expansion de l'historique n'est pas particulièrement utile.
G-Man dit 'Réinstalle Monica' le