Comment puis-je exécuter la dernière ligne de sortie dans bash?

12

Je sais que je peux exécuter la dernière commande dans bash, avec !!, mais comment puis-je exécuter la dernière ligne de sortie?

Je pense au cas d'utilisation de cette sortie:

The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

Mais je ne sais pas comment je peux faire ça. Je pense à quelque chose comme le !!, peut-être @@ou similaire?


Super User a aussi cette question.

Tim
la source
1
Pourquoi ne le copiez-vous pas et ne le collez-vous pas?
Pilot6
1
@Tim Vous voulez des moyens d'exécuter la dernière ligne de la sortie d'un programme, mais pour ce cas d'utilisation particulier, il y a aussi l'approche consistant à changer la command_not_found_handlefonction shell ou un script qu'il exécute (généralement juste /usr/lib/command-not-found). Je pense que vous pourriez faire en sorte que vous soyez invité de manière interactive avec l'option d'installer automatiquement le package, ou (probablement mieux) afin que le nom du package soit stocké quelque part et puisse être installé en exécutant une commande courte. C'est assez différent de ce que vous avez demandé ici, vous pourriez envisager de poster une question distincte à ce sujet.
Eliah Kagan
2
Pourrait également être pertinent: github.com/nvbn/thefuck (Ignorer le nom) cet outil corrige automatiquement les commandes git / apt / etc :)
bhathiya-perera

Réponses:

16

La commande $(!! |& tail -1)devrait faire:

$ git something
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

$ $(!! |& tail -1)
$(git something |& tail -1)
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
git-man liberror-perl

Comme vous pouvez le voir, la sudo apt-get install gitcommande est en cours d'exécution.

EDIT: décomposer$(!! |& tail -1)

  • $()est le bashmodèle de substitution de commande

  • bashs'étendra !!à la dernière commande exécutée

  • |&une partie est délicate. Normalement, pipe |prendra le STDOUT de la commande du côté gauche et le passera en tant que STDIN à la commande du côté droit de |, dans votre cas, la commande précédente imprime sa sortie sur STDERR en tant que message d'erreur. Donc, faire |n'aiderait pas, nous devons passer à la fois STDOUT et STDERR (ou seulement STDERR) à la commande du côté droit. |&passera le STDOUT et le STDERR à la fois comme le STDIN à la commande du côté droit. Alternativement, mieux vous pouvez seulement passer le STDERR:

    $(!! 2>&1 >/dev/null | tail -1)
  • tail -1comme d'habitude, imprimera la dernière ligne de son entrée. Ici, plutôt que d'imprimer la dernière ligne, vous pouvez être plus précis, comme imprimer la ligne qui contient la apt-get installcommande:

    $(!! 2>&1 >/dev/null | grep 'apt-get install')
heemayl
la source
1
Votre approche suppose que la sortie sera identique la deuxième fois. Certaines commandes peuvent produire une sortie différente la prochaine fois, auquel cas il est très difficile de prédire ce que l'exécution de la dernière ligne de sa sortie va réellement faire.
kasperd
1
@kasperd Vous avez raison..althouh en ce qui concerne cet exemple spécifique, la sortie doit rester la même ..
heemayl
7

TL; DR: alias @@='$($(fc -ln -1) |& tail -1)'

Les fonctionnalités d'interaction de l'historique de Bash n'offrent aucun mécanisme pour examiner la sortie des commandes. Le shell ne stocke pas cela , et l'expansion de l'historique est spécifiquement pour les commandes que vous avez vous-même exécutées, ou des parties de ces commandes.

Cela laisse l'approche de réexécuter la dernière commande et de canaliser à la fois stdout et stderr ( |&) dans une substitution de commande. La réponse de heemayl y parvient, mais ne peut pas être utilisée dans un alias car le shell effectue une expansion de l'historique avant de développer les alias, et non après.

Je ne peux pas non plus faire en sorte que l'expansion de l'historique fonctionne dans une fonction shell, même en l'activant dans la fonction avec set -H. Je soupçonne !!qu'une fonction ne sera jamais développée, et je ne sais pas à quoi elle serait développée si elle l'était, mais pour l'instant je ne sais pas exactement pourquoi elle ne l'est pas.

Par conséquent, si vous souhaitez configurer les choses afin de pouvoir le faire avec très peu de frappe, vous devez utiliser le fcshell intégré au lieu de l'expansion de l'historique pour extraire la dernière commande de l'historique. Cela présente l'avantage supplémentaire de fonctionner même lorsque l'expansion de l'historique est désactivée.

Comme le montre la réponse de Gordon Davisson à Créer un alias contenant l'expansion de l'historique bash (sur Super User ), simule . Dans ce pour brancher dans la commande de heemayl les rendements:$(fc -ln -1)!!!! $(!! |& tail -1)

$($(fc -ln -1) |& tail -1)

Cela fonctionne comme $(!! |& tail -1)mais peut aller dans une définition d'alias:

alias @@='$($(fc -ln -1) |& tail -1)'

Après avoir exécuté cette définition, ou l'avoir placée dans .bash_aliasesou .bashrcet démarrer un nouveau shell, vous pouvez simplement taper @@(ou tout ce que vous avez nommé l'alias) pour tenter d'exécuter la dernière ligne de sortie de la dernière commande.

ek@Io:~$ alias @@='$($(fc -ln -1) |& tail -1)'
ek@Io:~$ evolution
The program 'evolution' is currently not installed. You can install it by typing:
sudo apt-get install evolution
ek@Io:~$ @@
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  evolution-common evolution-data-server evolution-data-server-online-accounts
....
Eliah Kagan
la source
Y a-t-il une option pour déplacer ceci vers un script séparé? J'ai essayé cela et j'ai finalement échoué, car fc ne produit rien ... Comme fc suit l'historique de la session shell en cours, donc le déplacer vers un script séparé ouvre une session différente là-bas avec un historique vide, bien sûr ... J'en ai ajouté quelques-uns commandes au-dessus de la ligne fc dans le script, et l'une d'elles a été exécutée. Donc, je me demande s'il y a une option pour dire au script, que son "contexte" est la session bash, qui l'a exécuté. Comme, quelques arguments pour #! / Bin / bash ou quelque chose ...
Exterminator13
@ Exterminator13 Si vous voulez dire un script distinct que vous exécutez - comme ./script, pas celui que vous source avec le .ou sourceintégré - je ne suis pas sûr. Bash s'ajoute à un fichier à la sortie du shell ou lorsque vous exécutez historyavec -aou -w(voir help history). Si c'était le cas, les commandes dans plusieurs shells interactifs seraient entrelacées (apparaissant dans l'ordre dans lequel vous les avez exécutées). Vous pouvez le faire ajouter immédiatement. . Mais je pense qu'il y a plus à faire pour faire fcfonctionner un script. Je suggère de poster une nouvelle question à ce sujet. Si vous le faites, n'hésitez pas à commenter ici avec un lien vers celui-ci.
Eliah Kagan
2

Si vous utilisez un émulateur de terminal sous X, comme gnome-terminal, konsoleou xterm, vous pouvez utiliser la sélection X pour quelque chose comme copier-coller:

Utiliser la sélection principale

Sélectionnez la ligne entière à exécuter avec un triple clic gauche .

Ensuite , collez - le à la ligne de commande à l' aide d' un bouton central de la souris .

Cela devrait fonctionner dans la plupart des émulateurs de terminaux. Il utilise la sélection principale, sans toucher au presse-papiers - ils sont séparés.
C'est sélectionner, puis combiner copier et coller en une seule opération, techniquement.

Volker Siegel
la source
1
@Tim C'est vrai - je vais le dire clairement.
Volker Siegel
1

Comme Eliah Kagan l'a mentionné , le shell ne stocke pas la sortie de la commande. Cependant, il existe un moyen d'obtenir la dernière ligne de sortie de la dernière commande, sans réexécuter la commande , si vous exécutez screen .

Mettez les lignes suivantes en vous ~/.screenrc:

register t "^a[k0y$ ^a]^a^L"
bind t process t

Ensuite, vous pouvez appuyer sur Ctrl+ atpour insérer la ligne précédente dans l'invite actuelle. Il serait possible de faire cela automatiquement en appuyant sur Entrée, mais c'est probablement une meilleure idée de le vérifier avant de lancer et d'appuyer simplement sur Entrée manuellement.

Comment ça fonctionne:

  • register tenregistre une chaîne dans un tampon nommé t. La chaîne qui suit est une séquence de touches.
  • bind tse lie à la clé t(c'est-à-dire s'exécute en utilisant Ctrl+ at), process tsignifie évaluer le contenu du tampon nommé t.
  • La séquence elle-même signifie:
    • ^a[(= Ctrla[): passer en mode copie
    • kremonter d'une ligne, 0aller au début de la ligne, y$copier jusqu'à la fin de la ligne, (espace) terminer la commande
    • ^a](= Ctrla]): coller le contenu copié
    • ^a^L(= CtrlaCtrlL) rafraîchit l'écran (sinon le contenu collé ne s'affichera pas, car vous ne l'avez pas saisi vous-même
atextor
la source