Automatische Erfassung der Ausgabe des letzten Befehls in eine Variable mit Bash?


139

Ich möchte das Ergebnis des zuletzt ausgeführten Befehls in einem nachfolgenden Befehl verwenden können. Beispielsweise,

$ find . -name foo.txt
./home/user/some/directory/foo.txt

Angenommen, ich möchte die Datei in einem Editor öffnen oder löschen oder etwas anderes damit tun können, z

mv <some-variable-that-contains-the-result> /some/new/location

Wie kann ich es tun? Vielleicht mit einer Bash-Variablen?

Aktualisieren:

Zur Verdeutlichung möchte ich die Dinge nicht manuell zuweisen. Was ich suche, sind so etwas wie eingebaute Bash-Variablen, z

ls /tmp
cd $_

$_enthält das letzte Argument des vorherigen Befehls. Ich möchte etwas Ähnliches, aber mit der Ausgabe des letzten Befehls.

Letzte Aktualisierung:

Seths Antwort hat ganz gut funktioniert. Einige Dinge zu beachten:

  • Vergessen Sie nicht, touch /tmp/xwenn Sie die Lösung zum ersten Mal ausprobieren
  • Das Ergebnis wird nur gespeichert, wenn der Exit-Code des letzten Befehls erfolgreich war

Nachdem ich Ihre Bearbeitung gesehen hatte, dachte ich, meine Antwort zu löschen. Ich frage mich, ob Sie etwas eingebaut haben, nach dem Sie suchen.
Aufgabe

Ich konnte nichts Eingebautes finden. Ich habe mich gefragt, ob es möglich wäre, es zu implementieren. Vielleicht über .bahsrc? Ich denke, es wäre eine ziemlich praktische Funktion.
Armandino

Ich fürchte, Sie können die Ausgabe entweder entweder in eine Datei oder eine Pipe umleiten oder erfassen, sonst wird sie nicht gespeichert.
Bandi

3
Sie können dies nicht ohne die Zusammenarbeit von Shell und Terminal tun, und sie kooperieren im Allgemeinen nicht. Siehe auch Wie verwende ich die letzte Ausgabe über die Befehlszeile wieder? und Verwenden von Text aus der Ausgabe früherer Befehle unter Unix Stack Exchange .
Gilles 'SO - hör auf böse zu sein'

6
Einer der Hauptgründe, warum die Ausgabe von Befehlen nicht erfasst wird, ist, dass die Ausgabe beliebig groß sein kann - viele Megabyte gleichzeitig. Zugegeben, nicht immer so große, aber große Ausgaben verursachen Probleme.
Jonathan Leffler

Antworten:


71

Dies ist eine wirklich hackige Lösung, aber sie scheint manchmal meistens zu funktionieren. Während des Testens stellte ich fest, dass es manchmal nicht sehr gut funktionierte, wenn ein ^CBefehlszeile angezeigt wurde, obwohl ich es ein wenig optimiert habe, um mich ein bisschen besser zu verhalten.

Dieser Hack ist nur ein interaktiver Hack, und ich bin ziemlich sicher, dass ich ihn niemandem empfehlen würde. Hintergrundbefehle verursachen wahrscheinlich noch weniger definiertes Verhalten als normal. Die anderen Antworten sind eine bessere Möglichkeit, programmgesteuert zu Ergebnissen zu gelangen.


Davon abgesehen ist hier die "Lösung":

PROMPT_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

Stellen Sie diese Bash-Umgebungsvariable ein und geben Sie die gewünschten Befehle aus. $LASThat normalerweise die Ausgabe, die Sie suchen:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve

1
@armandino: Dies führt natürlich dazu, dass Programme, die bei Standard-Out mit einem Terminal interagieren möchten, nicht wie erwartet funktionieren (mehr / weniger) oder seltsame Dinge in $ LAST (Emacs) speichern. Aber ich denke, es ist ungefähr so ​​gut, wie Sie es bekommen werden. Die einzige andere Möglichkeit besteht darin, mit dem Skript (Typ) eine Kopie von ALLES in einer Datei zu speichern und dann mit PROMPT_COMMAND nach Änderungen seit dem letzten PROMPT_COMMAND zu suchen. Dies schließt jedoch Dinge ein, die Sie nicht wollen. Ich bin mir ziemlich sicher, dass Sie nichts näher an dem finden werden, was Sie wollen.
Seth Robertson

2
Um noch ein bisschen weiter zu gehen: PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec >/dev/tty; exec > >(tee /tmp/last); exec 2>/dev/tty; exec 2> >(tee /tmp/lasterr)'das bietet sowohl $lastund $lasterr.
inkaphink

@cdosborn: Mann sendet standardmäßig die Ausgabe über einen Pager. Wie mein vorheriger Kommentar sagte: "Programme, die erwarten, mit einem Terminal bei Standard-Out zu interagieren, funktionieren nicht wie erwartet (mehr / weniger)". mehr und weniger sind Pager.
Seth Robertson

Ich bekomme:No such file or directory
Francisco Corrales Morales

@Raphink Ich wollte nur auf eine Korrektur hinweisen: Ihr Vorschlag sollte enden, 2> >(tee /tmp/lasterr 1>&2)da die Standardausgabe von teeauf Standardfehler zurückgeleitet werden muss.
Hugues

90

Ich kenne keine Variable, die dies automatisch tut . Um etwas anderes zu tun, als nur das Ergebnis zu kopieren, können Sie alles, was Sie gerade getan haben, erneut ausführen, z

vim $(!!)

Wo !!bedeutet die Erweiterung des Verlaufs "der vorherige Befehl"?

Wenn Sie erwarten, dass es einen einzelnen Dateinamen mit Leerzeichen oder anderen Zeichen gibt, die eine ordnungsgemäße Analyse der Argumente verhindern könnten, zitieren Sie das Ergebnis ( vim "$(!!)"). Wenn Sie es nicht in Anführungszeichen setzen, können mehrere Dateien gleichzeitig geöffnet werden, sofern sie keine Leerzeichen oder andere Shell-Parsing-Token enthalten.


1
Das Einfügen von Kopien ist das, was ich normalerweise in einem solchen Fall mache, auch weil Sie normalerweise nur einen Teil der Ausgabe des Befehls benötigen. Ich bin überrascht, dass Sie der erste sind, der es erwähnt.
Bruno De Fraine

4
Sie können das gleiche mit weniger Tastenanschlägen tun mit:vim `!!`
psmears

Um den Unterschied von der akzeptierten Antwort zu sehen, führen Sie aus date "+%N"und dannecho $(!!)
Marinos An

20

Bash ist eine hässliche Sprache. Ja, Sie können die Ausgabe einer Variablen zuweisen

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

Hoffen Sie jedoch besser, dass findnur ein Ergebnis zurückgegeben wird und dass dieses Ergebnis keine "ungeraden" Zeichen enthält, wie z. B. Zeilenumbrüche oder Zeilenvorschübe, da diese stillschweigend geändert werden, wenn sie einer Bash-Variablen zugewiesen werden.

Aber seien Sie besser vorsichtig, wenn Sie Ihre Variable korrekt zitieren, wenn Sie sie verwenden!

Es ist besser, direkt auf die Datei zu reagieren, z. B. mit find's -execdir(siehe Handbuch).

find -name foo.txt -execdir vim '{}' ';'

oder

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'

5
Sie werden beim Zuweisen nicht stillschweigend geändert, sondern beim Echo! Sie müssen nur tun, um echo "${MY_VAR}"zu sehen, dass dies der Fall ist.
Paxdiablo

13

Es gibt mehr als eine Möglichkeit, dies zu tun. Eine Möglichkeit ist die Verwendung, mit v=$(command)der die Befehlsausgabe zugewiesen wird v. Beispielsweise:

v=$(date)
echo $v

Und Sie können auch Backquotes verwenden.

v=`date`
echo $v

Aus dem Bash Beginners Guide ,

Wenn die Backquoted-Form der Substitution im alten Stil verwendet wird, behält der Backslash seine wörtliche Bedeutung bei, außer wenn "$", "` "oder" \ "folgen. Die ersten Backticks, denen kein Backslash vorangestellt ist, beenden die Befehlsersetzung. Bei Verwendung des Formulars "$ (COMMAND)" bilden alle Zeichen in Klammern den Befehl. keine werden speziell behandelt.

BEARBEITEN: Nach der Bearbeitung in der Frage scheint dies nicht das zu sein, wonach das OP sucht. Soweit ich weiß, gibt es keine spezielle Variable wie $_für die Ausgabe des letzten Befehls.


11

Es ist sehr leicht. Verwenden Sie Anführungszeichen:

var=`find . -name foo.txt`

Und dann können Sie das jederzeit in der Zukunft nutzen

echo $var
mv $var /somewhere

11

Disclamers:

  • Diese Antwort ist spät ein halbes Jahr: D.
  • Ich bin ein schwerer tmux-Benutzer
  • Sie müssen Ihre Shell in tmux ausführen, damit dies funktioniert

Wenn Sie eine interaktive Shell in tmux ausführen, können Sie problemlos auf die aktuell auf einem Terminal angezeigten Daten zugreifen. Schauen wir uns einige interessante Befehle an:

  • tmux-Erfassungsbereich : Dieser kopiert die angezeigten Daten in einen der internen Puffer des tmux. Es kann den Verlauf kopieren, der derzeit nicht sichtbar ist, aber das interessiert uns jetzt nicht
  • tmux list-buffer : Hier werden die Informationen zu den erfassten Puffern angezeigt. Der neueste hat die Nummer 0.
  • tmux show-buffer -b (buffer num) : Hiermit wird der Inhalt des angegebenen Puffers auf einem Terminal gedruckt
  • tmux paste-buffer -b (buffer num) : Fügt den Inhalt des angegebenen Puffers als Eingabe ein

Ja, das gibt uns jetzt viele Möglichkeiten :) Ich habe einen einfachen Alias ​​eingerichtet: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1"und jetzt benutze ich jedes Mal, wenn ich auf die letzte Zeile zugreifen muss, einfach $(L), um sie zu erhalten.

Dies ist unabhängig von dem vom Programm verwendeten Ausgabestream (stdin oder stderr), der Druckmethode (ncurses usw.) und dem Exit-Code des Programms - die Daten müssen nur angezeigt werden.


Hey, danke für diesen tmux-Tipp. Ich suchte im Grunde das Gleiche wie das OP, fand Ihren Kommentar und codierte ein Shell-Skript, mit dem Sie einen Teil der Ausgabe eines vorherigen Befehls mit den von Ihnen erwähnten tmux-Befehlen und Vim-Bewegungen auswählen und einfügen konnten: github.com/ bgribble / lw
Bill Gribble

9

Ich denke, Sie können möglicherweise eine Lösung finden, bei der Sie Ihre Shell auf ein Skript setzen, das Folgendes enthält:

#!/bin/sh
bash | tee /var/log/bash.out.log

Wenn Sie dann $PROMPT_COMMANDein Trennzeichen ausgeben möchten, können Sie eine Hilfsfunktion (möglicherweise aufgerufen _) schreiben , mit der Sie den letzten Teil dieses Protokolls erhalten, sodass Sie es wie folgt verwenden können:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again

7

Sie können den folgenden Alias ​​in Ihrem Bash-Profil einrichten:

alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

Wenn Sie dann nach einem beliebigen Befehl 's' eingeben, können Sie das Ergebnis in einer Shell-Variablen 'it' speichern.

Beispielverwendung wäre also:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'

Danke dir! Dies ist , was ich brauchte , um meine implementieren grabFunktion , dass Kopien n - te Zeile von den letzten Befehl in der Zwischenablage gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq

6

Ich habe diese bashFunktion gerade aus den Vorschlägen hier heraus destilliert :

grab() {     
  grab=$("$@")
  echo $grab
}

Dann machst du einfach:

> grab date
Do 16. Feb 13:05:04 CET 2012
> echo $grab
Do 16. Feb 13:05:04 CET 2012

Update : Ein anonymer Benutzer, dessen Ersetzung vorgeschlagen echowurde printf '%s\n', hat den Vorteil, dass er keine Optionen wie -eim erfassten Text verarbeitet. Wenn Sie also solche Besonderheiten erwarten oder erleben, ziehen Sie diesen Vorschlag in Betracht. Eine andere Option ist cat <<<$grabstattdessen zu verwenden .


1
Ok, genau genommen ist dies keine Antwort, da der "automatische" Teil komplett fehlt.
Tilman Vogel

Können Sie die cat <<<$grabOption erklären ?
Leoj

1
Zitat aus man bash: "Here Strings: Eine Variante der Here-Dokumente. Das Format lautet: <<<wordDas Wort wird erweitert und dem Befehl an seiner Standardeingabe übergeben." Der Wert von $grabwird also catauf stdin eingespeist und catnur an stdout zurückgemeldet.
Tilman Vogel

5

Mit "Ich möchte das Ergebnis des zuletzt ausgeführten Befehls in einem nachfolgenden Befehl verwenden können" gehe ich davon aus, dass Sie das Ergebnis eines Befehls meinen, nicht nur finden.

Wenn das der Fall ist - xargs ist das, wonach Sie suchen.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

ODER wenn Sie daran interessiert sind, zuerst die Ausgabe zu sehen:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Dieser Befehl behandelt mehrere Dateien und funktioniert wie ein Zauber, selbst wenn der Pfad und / oder der Dateiname Leerzeichen enthalten.

Beachten Sie den Teil mv {} / some / new / location / {} des Befehls. Dieser Befehl wird für jede Zeile erstellt und ausgeführt, die mit einem früheren Befehl gedruckt wurde. Hier wird die durch einen früheren Befehl gedruckte Zeile anstelle von {} ersetzt .

Auszug aus der Manpage von xargs:

xargs - Befehlszeilen aus Standardeingaben erstellen und ausführen

Weitere Informationen finden Sie in der Manpage: man xargs


5

Erfassen Sie die Ausgabe mit Backticks:

output=`program arguments`
echo $output
emacs $output

4

Normalerweise mache ich das, was die anderen hier vorgeschlagen haben ... ohne die Aufgabe:

$find . -iname '*.cpp' -print
./foo.cpp
./bar.cpp
$vi `!!`
2 files to edit

Sie können schicker werden, wenn Sie möchten:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`

2

Wenn Sie nur Ihren letzten Befehl erneut ausführen und die Ausgabe erhalten möchten, funktioniert eine einfache Bash-Variable:

LAST=`!!`

Dann können Sie Ihren Befehl für die Ausgabe ausführen mit:

yourCommand $LAST

Dies erzeugt einen neuen Prozess und führt Ihren Befehl erneut aus. Anschließend erhalten Sie die Ausgabe. Es klingt so, als ob Sie wirklich eine Bash-Verlaufsdatei für die Befehlsausgabe wünschen würden. Dies bedeutet, dass Sie die Ausgabe erfassen müssen, die Bash an Ihr Terminal sendet. Sie könnten etwas schreiben, um die notwendigen / dev oder / proc zu sehen, aber das ist chaotisch. Sie können auch einfach eine "spezielle Pipe" zwischen Ihrem Term und Bash mit einem Tee-Befehl in der Mitte erstellen, der zu Ihrer Ausgabedatei umleitet.

Aber beide sind Hacky-Lösungen. Ich denke, das Beste wäre der Terminator , ein moderneres Terminal mit Ausgabeprotokollierung. Überprüfen Sie einfach Ihre Protokolldatei auf die Ergebnisse des letzten Befehls. Eine Bash-Variable ähnlich der oben genannten würde dies noch einfacher machen.


1

Hier ist eine Möglichkeit, dies zu tun, nachdem Sie Ihren Befehl ausgeführt und entschieden haben, dass Sie das Ergebnis in einer Variablen speichern möchten:

$ find . -name foo.txt
./home/user/some/directory/foo.txt
$ OUTPUT=`!!`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
$ mv $OUTPUT somewhere/else/

Oder wenn Sie im Voraus wissen, dass Sie das Ergebnis in einer Variablen haben möchten, können Sie Backticks verwenden:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt

1

Alternativ zu den vorhandenen Antworten: Verwenden whileSie diese Option, wenn Ihre Dateinamen Leerzeichen wie das folgende enthalten können:

find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

Wie ich schrieb, ist der Unterschied nur relevant, wenn Sie Leerzeichen in den Dateinamen erwarten müssen.

NB: Das einzige integrierte Material betrifft nicht die Ausgabe, sondern den Status des letzten Befehls.


1

Sie können verwenden !!: 1. Beispiel:

~]$ ls *.~
class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 

~]$ rm !!:1
rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 


~]$ ls file_to_remove1 file_to_remove2
file_to_remove1 file_to_remove2

~]$ rm !!:1
rm file_to_remove1

~]$ rm !!:2
rm file_to_remove2

Diese Notation greift das nummerierte Argument eines Befehls auf: Siehe Dokumentation zu "$ {parameter: offset}" hier: gnu.org/software/bash/manual/html_node/… . Siehe auch hier für weitere Beispiele: howtogeek.com/howto/44997/…
Alexander Bird

1

Ich hatte ein ähnliches Bedürfnis, bei dem ich die Ausgabe des letzten Befehls in den nächsten verwenden wollte. Ähnlich wie ein | (Rohr). z.B

$ which gradle 
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle

zu so etwas wie -

$ which gradle |: ls -altr {}

Lösung: Erstellt diese benutzerdefinierte Pipe. Ganz einfach mit xargs -

$ alias :='xargs -I{}'

Grundsätzlich nichts, indem man eine kurze Hand für Xargs erstellt, es funktioniert wie Charme und ist wirklich praktisch. Ich füge nur den Alias ​​in die Datei .bash_profile ein.


1

Dies kann mithilfe der Magie der Dateideskriptoren und der lastpipeShell-Option erfolgen.

Dies muss mit einem Skript erfolgen - die Option "lastpipe" funktioniert im interaktiven Modus nicht.

Hier ist das Skript, mit dem ich getestet habe:

$ cat shorttest.sh 
#!/bin/bash
shopt -s lastpipe

exit_tests() {
    EXITMSG="$(cat /proc/self/fd/0)"
}

ls /bloop 2>&1 | exit_tests

echo "My output is \"$EXITMSG\""


$ bash shorttest.sh 
My output is "ls: cannot access '/bloop': No such file or directory"

Was ich hier mache ist:

  1. Einstellen der Shell-Option shopt -s lastpipe. Ohne dies funktioniert es nicht, da Sie den Dateideskriptor verlieren.

  2. Stellen Sie sicher, dass mein stderr auch mit erfasst wird 2>&1

  3. Weiterleiten der Ausgabe an eine Funktion, damit auf den Standarddateideskriptor verwiesen werden kann.

  4. Festlegen der Variablen durch Abrufen des Inhalts des /proc/self/fd/0Dateideskriptors, der stdin ist.

Ich verwende dies zum Erfassen von Fehlern in einem Skript. Wenn also ein Problem mit einem Befehl auftritt, kann ich die Verarbeitung des Skripts beenden und sofort beenden.

shopt -s lastpipe

exit_tests() {
    MYSTUFF="$(cat /proc/self/fd/0)"
    BADLINE=$BASH_LINENO
}

error_msg () {
    echo -e "$0: line $BADLINE\n\t $MYSTUFF"
    exit 1
}

ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

Auf diese Weise kann ich 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msghinter jedem Befehl, den ich überprüfen möchte, etwas hinzufügen .

Jetzt können Sie Ihre Ausgabe genießen!


0

Dies ist keine reine Bash-Lösung, aber Sie können Piping mit sed verwenden, um die letzte Zeile der vorherigen Befehle auszugeben.

Lassen Sie uns zuerst sehen, was ich im Ordner "a" habe.

rasjani@helruo-dhcp022206::~$ find a
a
a/foo
a/bar
a/bat
a/baz
rasjani@helruo-dhcp022206::~$ 

Dann würde sich Ihr Beispiel mit ls und cd in sed & piping in so etwas verwandeln:

rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'`
rasjani@helruo-dhcp022206::~/a/baz$ pwd
/home/rasjani/a/baz
rasjani@helruo-dhcp022206::~/a/baz$

Die eigentliche Magie geschieht also mit sed. Sie leiten die Ausgabe eines Befehls in sed weiter und sed druckt die letzte Zeile, die Sie als Parameter mit Back Ticks verwenden können. Oder Sie können das auch mit xargs kombinieren. ("man xargs" in der Shell ist dein Freund)


0

Die Shell verfügt nicht über perlähnliche Spezialsymbole, die das Echo-Ergebnis des letzten Befehls speichern.

Lernen Sie, das Pfeifensymbol mit awk zu verwenden.

find . | awk '{ print "FILE:" $0 }'

Im obigen Beispiel könnten Sie Folgendes tun:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'

0
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

ist noch ein anderer Weg, wenn auch schmutzig.


finden . -name foo.txt 1> tmpfile && mv `cat tmpfile` path / to / some / dir && rm tmpfile
Kevin

0

Ich finde es etwas ärgerlich, daran zu denken, die Ausgabe meiner Befehle in eine bestimmte Datei zu leiten. Meine Lösung ist eine Funktion in meiner .bash_profile, die die Ausgabe in einer Datei abfängt und das Ergebnis zurückgibt, wenn Sie es benötigen.

Der Vorteil bei diesem ist, dass Sie nicht den gesamten Befehl erneut findausführen müssen (wenn Sie oder andere lang laufende Befehle verwenden, die kritisch sein können).

Einfach genug, fügen Sie dies in Ihre .bash_profile:

Skript

# catch stdin, pipe it to stdout and save to a file
catch () { cat - | tee /tmp/catch.out}
# print whatever output was saved to a file
res () { cat /tmp/catch.out }

Verwendung

$ find . -name 'filename' | catch
/path/to/filename

$ res
/path/to/filename

An diesem Punkt füge ich in der Regel nur | catchdas Ende aller meiner Befehle hinzu, da dies keine Kosten verursacht und ich keine Befehle erneut ausführen muss, deren Fertigstellung lange dauert.

Wenn Sie die Dateiausgabe in einem Texteditor öffnen möchten, können Sie Folgendes tun:

# vim or whatever your favorite text editor is
$ vim <(res)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.