Wie kann ich die letzte Ausgabezeile in Bash ausführen?

12

Ich weiß, dass ich den letzten Befehl in bash mit !!ausführen kann, aber wie kann ich die letzte Ausgabezeile ausführen?

Ich denke an den Anwendungsfall dieser Ausgabe:

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

Aber ich weiß nicht, wie ich das machen kann. Ich denke an so etwas !!, vielleicht @@oder ähnliches?


Super User hat auch diese Frage.

Tim
quelle
1
Warum kopierst du es nicht einfach und fügst es ein?
Pilot6
1
@Tim Sie möchten Möglichkeiten, die letzte Zeile der Programmausgabe auszuführen, aber für diesen speziellen Anwendungsfall gibt es auch den Ansatz, die command_not_found_handleShell-Funktion oder ein Skript zu ändern, das sie ausführt (normalerweise nur /usr/lib/command-not-found). Ich denke, Sie könnten es schaffen, damit Sie interaktiv mit der Option aufgefordert werden, das Paket automatisch zu installieren, oder (wahrscheinlich besser), damit der Name des Pakets irgendwo gespeichert wird und durch Ausführen eines kurzen Befehls installiert werden kann. Das unterscheidet sich genug von dem, was Sie hier gefragt haben. Sie können eine separate Frage dazu stellen.
Eliah Kagan
2
Könnte auch relevant sein: github.com/nvbn/thefuck (Ignoriere den Namen) Dieses Tool korrigiert automatisch git / apt / etc Befehle :)
bhathiya-perera

Antworten:

16

Der Befehl $(!! |& tail -1)sollte Folgendes tun:

$ 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

Wie Sie sehen, wird der sudo apt-get install gitBefehl ausgeführt.

EDIT: Breaking down$(!! |& tail -1)

  • $()ist das bashBefehlsersetzungsmuster

  • bashwird !!auf den zuletzt ausgeführten Befehl erweitert

  • |&Teil ist schwierig. Normalerweise nimmt Pipe |das STDOUT des linksseitigen Befehls und übergibt es als STDIN an den Befehl auf der rechten Seite von |. In Ihrem Fall druckt der vorherige Befehl seine Ausgabe auf STDERR als Fehlermeldung. Also, tun |würde nicht helfen, müssen wir sowohl die STDOUT und STDERR (oder nur STDERR) auf der rechten Seite seitige Befehl zu übergeben. |&Übergibt STDOUT und STDERR als STDIN an den rechtsseitigen Befehl. Alternativ können Sie besser nur den STDERR bestehen:

    $(!! 2>&1 >/dev/null | tail -1)
  • tail -1Wie üblich wird die letzte Zeile von der Eingabe gedruckt. Anstatt die letzte Zeile zu drucken, können Sie präziser sein als die Zeile, die den apt-get installBefehl enthält :

    $(!! 2>&1 >/dev/null | grep 'apt-get install')
heemayl
quelle
1
Ihr Ansatz geht davon aus, dass die Ausgabe beim zweiten Mal identisch ist. Einige Befehle können beim nächsten Mal eine andere Ausgabe erzeugen. In diesem Fall ist es sehr schwierig vorherzusagen, was die Ausführung der letzten Zeile ihrer Ausgabe tatsächlich bewirken wird.
Kasperd
1
@kasperd Sie haben Recht .. obwohl, was dieses spezielle Beispiel betrifft, sollte die Ausgabe gleich bleiben ..
heemayl
7

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

Bash Geschichte Interaktionsmöglichkeiten bieten keinen Mechanismus , um die zu untersuchen Ausgabe von Befehlen. Die Shell speichert das nicht und die Verlaufserweiterung ist speziell für Befehle, die Sie selbst ausführen lassen, oder Teile dieser Befehle.

Dies lässt den Ansatz, den letzten Befehl erneut auszuführen und sowohl stdout als auch stderr ( |&) in eine Befehlssubstitution zu leiten . heemayls Antwort erreicht dies, kann jedoch nicht in einem Alias ​​verwendet werden, da die Shell die Verlaufserweiterung vor dem Erweitern von Aliasen und nicht danach durchführt.

Ich kann die Verlaufserweiterung auch nicht in einer Shell-Funktion zum Laufen bringen, selbst wenn ich sie in der Funktion mit aktiviere set -H. Ich vermute, dass !!eine Funktion niemals erweitert wird, und ich bin mir nicht sicher, wohin sie erweitert werden würde, aber im Moment bin ich mir nicht sicher , warum dies nicht der Fall ist.

Wenn Sie die Dinge so einrichten möchten, dass Sie dies mit sehr wenig Eingabe tun können, sollten Sie daher die integrierte fcShell anstelle der Verlaufserweiterung verwenden, um den letzten Befehl aus dem Verlauf zu extrahieren. Dies hat den zusätzlichen Vorteil, dass es auch dann funktioniert, wenn die Verlaufserweiterung deaktiviert ist.

Wie in gezeigt Gordon Davisson ‚s Antwort auf eine Alias Erstellen enthält Bash Geschichte Erweiterung (auf Super User ), $(fc -ln -1)simuliert !!. Anstecken diese in für !!in heemayl Befehl $(!! |& tail -1) ergibt:

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

Dies funktioniert wie $(!! |& tail -1), kann aber in einer Alias-Definition enthalten sein:

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

Nachdem Sie diese Definition ausgeführt oder in .bash_aliasesoder .bashrceingefügt und eine neue Shell gestartet haben, können Sie einfach @@(oder wie auch immer Sie den Alias ​​genannt haben) eingeben, um zu versuchen, die letzte Ausgabezeile des letzten Befehls auszuführen.

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
quelle
Gibt es eine Option, um dies in ein separates Skript zu verschieben? Ich habe das versucht und bin schließlich gescheitert, weil fc nichts ausgibt ... Da fc dem Verlauf der aktuellen Shell-Sitzung folgt, öffnet das Verschieben in ein separates Skript dort natürlich eine andere Sitzung mit leerem Verlauf ... Ich habe einige hinzugefügt Befehle über der fc-Zeile im Skript, und einer von ihnen wurde ausgeführt. Ich frage mich also, ob es eine Option gibt, dem Skript mitzuteilen, dass sein "Kontext" die Bash-Sitzung ist, die es ausgeführt hat. Wie einige Argumente für #! / Bin / bash oder so ...
Exterminator13
Wenn Sie ein separates Skript bedeuten @ Exterminator13 , dass Sie laufen --like ./script, nicht , dass Sie Quelle mit der .oder sourcebuiltin - ich bin nicht sicher. Bash wird beim Beenden der Shell oder beim Ausführen historymit -aoder an eine Datei angehängt -w(siehe help history). In diesem Fall würden Befehle in mehreren interaktiven Shells verschachtelt (in der Reihenfolge, in der Sie sie ausgeführt haben). Sie können es sofort anhängen lassen. . Aber ich denke, es gibt noch mehr zu tun, um fcin einem Skript zu arbeiten. Ich schlage vor, eine neue Frage dazu zu stellen. Wenn Sie dies tun, können Sie dies hier mit einem Link kommentieren.
Eliah Kagan
2

Wenn Sie einen Terminal - Emulator unter X verwenden, wie gnome-terminal, konsoleoder xtermkönnen Sie die X - Auswahl für so etwas wie Kopie verwenden und fügen Sie ihn:

Verwenden Sie die primäre Auswahl

Wählen Sie die gesamte Zeile aus , die mit einem dreifachen Linksklick ausgeführt werden soll .

Dann fügen Sie ihn in die Befehlszeile unter Verwendung eines mittleren Knopf klicken .

Dies sollte in den meisten Terminalemulatoren funktionieren. Es nutzt die primäre Auswahl, ohne die Zwischenablage zu berühren - sie sind getrennt.
Es wird ausgewählt und dann technisch in einem Arbeitsgang kopiert und eingefügt.

Volker Siegel
quelle
1
@ Tim Das stimmt - ich werde das klarstellen.
Volker Siegel
1

Wie Eliah Kagan erwähnte , speichert die Shell die Befehlsausgabe nicht. Es gibt jedoch eine Möglichkeit, die letzte Ausgabezeile des letzten Befehls abzurufen, ohne den Befehl erneut auszuführen, wenn Sie den Bildschirm ausführen .

Fügen Sie die folgenden Zeilen ein ~/.screenrc:

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

Anschließend können Sie Ctrl+ drücken at, um die vorherige Zeile in die aktuelle Eingabeaufforderung einzufügen. Es ist möglich, dass dies auch automatisch die Eingabetaste drückt, aber es ist wahrscheinlich eine bessere Idee, dies vor dem Ausführen zu überprüfen und einfach die Eingabetaste manuell zu drücken.

Wie es funktioniert:

  • register tregistriert eine Zeichenfolge in einem Puffer mit dem Namen t. Die folgende Zeichenfolge ist eine Tastenfolge.
  • bind tbindet an den Schlüssel t(dh mit Ctrl+ ausführen at), process tbedeutet, den Inhalt des genannten Puffers auszuwerten t.
  • Die Sequenz selbst bedeutet:
    • ^a[(= Ctrla[): Kopiermodus aufrufen
    • kGehen Sie eine Zeile nach oben, 0gehen Sie zum Anfang der Zeile, y$kopieren Sie bis zum Ende der Zeile, (Leerzeichen) führen Sie den Befehl aus
    • ^a](= Ctrla]): Kopierten Inhalt einfügen
    • ^a^L(= CtrlaCtrlL) Aktualisieren Sie den Bildschirm (andernfalls wird der eingefügte Inhalt nicht angezeigt, da Sie ihn nicht selbst eingegeben haben
atextor
quelle