Automatycznie przechwytywać dane wyjściowe ostatniego polecenia do zmiennej za pomocą Bash?

139

Chciałbym móc użyć wyniku ostatnio wykonanego polecenia w kolejnym poleceniu. Na przykład,

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

Teraz powiedzmy, że chcę móc otworzyć plik w edytorze, usunąć go lub zrobić z nim coś innego, np.

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

Jak mogę to zrobić? Może używając jakiejś zmiennej bash?

Aktualizacja:

Aby wyjaśnić, nie chcę przypisywać rzeczy ręcznie. To, czego szukam, to coś w rodzaju wbudowanych zmiennych bash, np

ls /tmp
cd $_

$_przechowuje ostatni argument poprzedniego polecenia. Chcę czegoś podobnego, ale z wyjściem ostatniego polecenia.

Ostateczna aktualizacja:

Odpowiedź Setha zadziałała całkiem nieźle. Kilka rzeczy, o których należy pamiętać:

  • nie zapomnij o tym touch /tmp/x, próbując rozwiązania po raz pierwszy
  • wynik zostanie zapisany tylko wtedy, gdy kod zakończenia ostatniego polecenia powiódł się
armandino
źródło
Po obejrzeniu twojej zmiany pomyślałem o usunięciu odpowiedzi. Zastanawiam się, czy jest coś wbudowanego, czego szukasz.
taskinoor
Nie mogłem znaleźć nic wbudowanego. Zastanawiałem się, czy można by to zaimplementować… może przez .bahsrc? Myślę, że byłaby to całkiem przydatna funkcja.
armandino
Obawiam się, że wszystko, co możesz zrobić, to przekierować wynik do pliku lub potoku lub przechwycić go, w przeciwnym razie nie zostanie on zapisany.
bandi
3
Nie możesz tego zrobić bez współpracy powłoki i terminala, a one na ogół nie współpracują. Zobacz także Jak ponownie wykorzystać ostatnie dane wyjściowe z wiersza poleceń? i Używanie tekstu z danych wyjściowych poprzednich poleceń na Unix Stack Exchange .
SO- Gilles 'SO- przestań być zły'
6
Jednym z głównych powodów, dla których dane wyjściowe poleceń nie są przechwytywane, jest to, że dane wyjściowe mogą być dowolnie duże - wiele megabajtów na raz. To prawda, nie zawsze tak duże, ale duże wydajności powodują problemy.
Jonathan Leffler

Odpowiedzi:

71

To naprawdę hakerskie rozwiązanie, ale wydaje się, że czasami działa. Podczas testów zauważyłem, że czasami nie działało to zbyt dobrze, gdy otrzymywałem znak ^Cw wierszu poleceń, chociaż trochę go poprawiłem, aby zachowywał się trochę lepiej.

Ten hack to hack tylko w trybie interaktywnym i jestem pewien, że nie poleciłbym go nikomu. Polecenia w tle prawdopodobnie spowodują nawet mniej zdefiniowane zachowanie niż normalnie. Inne odpowiedzi to lepszy sposób na programowe uzyskiwanie wyników.


Biorąc to pod uwagę, oto „rozwiązanie”:

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

Ustaw tę zmienną środowiskową bash i wydaj polecenia zgodnie z potrzebami. $LASTzazwyczaj będzie miał wynik, którego szukasz:

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
Seth Robertson
źródło
1
@armandino: To oczywiście powoduje, że programy oczekujące interakcji z terminalem na wyjściu standardowym nie będą działać zgodnie z oczekiwaniami (więcej / mniej) lub przechowują dziwne rzeczy w $ LAST (emacs). Ale myślę, że to jest tak dobre, jak zamierzasz. Jedyną inną opcją jest użycie (typ) skryptu do zapisania kopii WSZYSTKIEGO do pliku, a następnie użycie PROMPT_COMMAND do sprawdzenia zmian od ostatniego PROMPT_COMMAND. Obejmuje to jednak rzeczy, których nie chcesz. Jestem jednak pewien, że nie znajdziesz niczego bliższego temu, czego chcesz.
Seth Robertson
2
Idź trochę dalej: 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)'co zapewnia zarówno $lasti $lasterr.
ℝaphink
@cdosborn: man domyślnie wysyła dane wyjściowe przez pager. Jak powiedział mój poprzedni komentarz: „programy oczekujące interakcji z terminalem na wyjściu standardowym nie będą działać zgodnie z oczekiwaniami (więcej / mniej)”. mniej i więcej to pagery.
Seth Robertson,
Dostaję:No such file or directory
Francisco Corrales Morales
@Raphink Chciałem tylko zwrócić uwagę na korektę: twoja sugestia powinna zakończyć się, 2> >(tee /tmp/lasterr 1>&2)ponieważ standardowe wyjście teemusi zostać przekierowane z powrotem do standardowego błędu.
Hugues
90

Nie znam żadnej zmiennej, która robi to automatycznie . Aby zrobić coś innego niż tylko skopiować i wkleić wynik, możesz ponownie uruchomić to, co właśnie zrobiłeś, np

vim $(!!)

Gdzie !!jest rozwinięcie historii oznaczające „poprzednie polecenie”.

Jeśli spodziewasz się, że będzie istniała pojedyncza nazwa pliku ze spacjami lub innymi znakami, które mogą uniemożliwić poprawne analizowanie argumentów, zacytuj wynik ( vim "$(!!)"). Pozostawienie go bez cudzysłowu umożliwi jednoczesne otwieranie wielu plików, o ile nie zawierają one spacji ani innych tokenów analizujących powłokę.

Daenyth
źródło
1
Kopiowanie i wklejanie jest tym, co zwykle robię w takim przypadku, również dlatego, że zazwyczaj potrzebujesz tylko części wyniku polecenia. Jestem zaskoczony, że jako pierwszy o tym wspomniałeś.
Bruno De Fraine
4
Możesz zrobić to samo z mniejszą liczbą naciśnięć klawiszy za pomocą:vim `!!`
psmears
Aby zobaczyć różnicę w stosunku do zaakceptowanej odpowiedzi, wykonaj, date "+%N"a następnieecho $(!!)
Marinos An
20

Bash to trochę brzydki język. Tak, możesz przypisać wyjście do zmiennej

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

Ale lepiej findmiej nadzieję, że najtrudniejszy wynik, który zwrócił tylko jeden wynik i że ten wynik nie zawiera żadnych "dziwnych" znaków, takich jak powrót karetki lub wysunięcie wiersza, ponieważ zostaną one po cichu zmodyfikowane po przypisaniu do zmiennej Bash.

Ale lepiej uważaj, aby poprawnie cytować zmienną podczas jej używania!

Lepiej działać na plik bezpośrednio, na przykład z find„s -execdir(zapoznać się z instrukcją).

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

lub

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'
rlibby
źródło
5
Nie są one modyfikowane po cichu podczas przypisywania, są modyfikowane, gdy echo! Wystarczy, że echo "${MY_VAR}"zobaczysz, że tak jest.
paxdiablo
13

Można to zrobić na więcej niż jeden sposób. Jednym ze sposobów jest użycie, v=$(command)które przypisze wyjście polecenia do v. Na przykład:

v=$(date)
echo $v

Możesz też używać odwrotnych cytatów.

v=`date`
echo $v

Z przewodnika Bash dla początkujących ,

Gdy używana jest forma podstawiania w starym stylu, zapisywana w cudzysłowie wstecznym, ukośnik odwrotny zachowuje swoje dosłowne znaczenie, z wyjątkiem sytuacji, gdy następuje po nim „$”, „„ ”lub„ \ ”. Pierwsze znaki odwrotne bez odwrotnego ukośnika kończą podstawianie poleceń. W przypadku użycia formy „$ (COMMAND)” wszystkie znaki między nawiasami tworzą polecenie; żaden nie jest traktowany specjalnie.

EDYCJA: Po edycji w pytaniu wydaje się, że nie jest to rzecz, której szuka PO. O ile wiem, nie ma specjalnej zmiennej, jak $_w przypadku ostatniego polecenia.

taskinoor
źródło
11

To całkiem proste. Użyj cudzysłowów wstecznych:

var=`find . -name foo.txt`

A potem możesz użyć tego w dowolnym momencie w przyszłości

echo $var
mv $var /somewhere
Wes Hardaker
źródło
11

Disclamers:

  • Ta odpowiedź jest późna pół roku: D
  • Jestem ciężkim użytkownikiem tmux
  • Aby to zadziałało, musisz uruchomić powłokę w tmux

Uruchamiając interaktywną powłokę w tmux, możesz łatwo uzyskać dostęp do danych aktualnie wyświetlanych na terminalu. Rzućmy okiem na kilka interesujących poleceń:

  • Panel przechwytywania tmux : ten kopiuje wyświetlane dane do jednego z wewnętrznych buforów tmux. Może skopiować historię, która obecnie nie jest widoczna, ale teraz nas to nie interesuje
  • tmux list-buffers : wyświetla informacje o przechwyconych buforach. Najnowszy będzie miał numer 0.
  • tmux show-buffer -b (numer bufora) : wypisuje zawartość podanego bufora na terminalu
  • tmux paste-buffer -b (numer bufora) : wkleja zawartość danego bufora jako dane wejściowe

Tak, daje nam to teraz wiele możliwości :) Jeśli chodzi o mnie, ustawiłem prosty alias: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1"i teraz za każdym razem, gdy potrzebuję uzyskać dostęp do ostatniej linii, po prostu używam, $(L)aby ją uzyskać.

Jest to niezależne od strumienia wyjściowego używanego przez program (czy to stdin, czy stderr), metody drukowania (ncurses itp.) I kodu wyjścia programu - wystarczy wyświetlić dane.

Wiesław Herr
źródło
Hej, dzięki za tę wskazówkę tmux. Szukałem w zasadzie tego samego co OP, znalazłem twój komentarz i zakodowałem skrypt powłoki, który pozwoli ci wybrać i wkleić część wyjścia poprzedniego polecenia za pomocą poleceń tmux, o których wspomniałeś, i ruchów Vima: github.com/ bgribble / lw
Bill Gribble
9

Myślę, że możesz być w stanie zhakować rozwiązanie, które wymaga ustawienia powłoki na skrypt zawierający:

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

Następnie, jeśli ustawisz $PROMPT_COMMANDwyjście separatora, możesz napisać funkcję pomocniczą (może nazywaną _), która pobierze ostatnią porcję tego dziennika, więc możesz jej używać w następujący sposób:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again
Jay Adkisson
źródło
7

Możesz ustawić następujący alias w swoim profilu bash:

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

Następnie, wpisując „s” po dowolnym poleceniu, można zapisać wynik w zmiennej powłoki „it”.

Tak więc przykładowe użycie to:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'
Joe Tallett
źródło
Dziękuję Ci! To jest to, czego potrzebowałem, aby zaimplementować moją grabfunkcję, która kopiuje n-ty wiersz z ostatniego polecenia do schowka gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq
6

Właśnie wydestylowałem tę bashfunkcję z sugestii tutaj:

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

Następnie po prostu wykonaj:

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

Aktualizacja : anonimowy użytkownik zasugerowany do zastąpienia echo, printf '%s\n'który ma tę zaletę, że nie przetwarza opcji, jak -ew pobranym tekście. Tak więc, jeśli spodziewasz się lub doświadczasz takich osobliwości, rozważ tę sugestię. Inną opcją jest użycie cat <<<$grabzamiast tego.

Tilman Vogel
źródło
1
Ok, mówiąc ściśle, to nie jest odpowiedź, ponieważ całkowicie brakuje części „automatycznej”.
Tilman Vogel
Czy możesz wyjaśnić tę cat <<<$grabopcję?
leoj
1
Cytując z man bash: „Tutaj Ciągi znaków: Wariant dokumentów tutaj, format: <<<wordSłowo jest interpretowane i dostarczane do polecenia na jego standardowym wejściu”. Tak więc wartość $grabjest przekazywana catna stdin i catpo prostu przekazuje ją z powrotem na standardowe wyjście.
Tilman Vogel
5

Mówiąc „Chciałbym móc użyć wyniku ostatnio wykonanego polecenia w kolejnym poleceniu”, zakładam - masz na myśli wynik dowolnego polecenia, a nie tylko znajdź.

Jeśli tak jest - xargs jest tym, czego szukasz.

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

LUB jeśli chcesz najpierw zobaczyć wyniki:

find . -name foo.txt -print0

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

To polecenie obsługuje wiele plików i działa jak urok, nawet jeśli ścieżka i / lub nazwa pliku zawiera spacje.

Zwróć uwagę na część polecenia mv {} / some / new / location / {} . To polecenie jest budowane i wykonywane dla każdej linii drukowanej przez wcześniejsze polecenie. Tutaj wiersz wydrukowany przez wcześniejsze polecenie jest zastępowany w miejsce {} .

Fragment ze strony podręcznika xargs:

xargs - buduje i wykonuje wiersze poleceń ze standardowego wejścia

Więcej szczegółów znajdziesz na stronie podręcznika: man xargs

ssapkota
źródło
5

Przechwyć dane wyjściowe za pomocą grawitacji:

output=`program arguments`
echo $output
emacs $output
bandi
źródło
4

Zwykle robię to, co sugerowali inni tutaj ... bez przydziału:

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

Możesz stać się bardziej wyhodowany, jeśli chcesz:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`
halm
źródło
2

Jeśli wszystko, czego chcesz, to ponowne uruchomienie ostatniego polecenia i uzyskanie wyniku, zadziała prosta zmienna bash:

LAST=`!!`

Więc możesz uruchomić polecenie na wyjściu za pomocą:

yourCommand $LAST

Spowoduje to powstanie nowego procesu i ponowne uruchomienie polecenia, a następnie da wynik. Wygląda na to, że naprawdę chciałbyś, aby był to plik historii bash do wyjścia polecenia. Oznacza to, że będziesz musiał przechwycić dane wyjściowe, które bash wysyła do twojego terminala. Mógłbyś napisać coś do obejrzenia / dev lub / proc, ale to niechlujne. Możesz także po prostu utworzyć „specjalną potokę” między swoim terminem a bash z poleceniem tee w środku, które przekierowuje do pliku wyjściowego.

Ale oba są rodzajem hackerskich rozwiązań. Myślę, że najlepszą rzeczą byłby terminator, który jest bardziej nowoczesnym terminalem z rejestrowaniem danych wyjściowych. Po prostu sprawdź plik dziennika, aby sprawdzić wyniki ostatniego polecenia. Zmienna bash podobna do powyższej uczyniłaby to jeszcze prostszym.

Spencer Rathbun
źródło
1

Oto jeden sposób, aby to zrobić po wykonaniu polecenia i zdecydowaniu, że chcesz zapisać wynik w zmiennej:

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

Lub jeśli wiesz z wyprzedzeniem, że chcesz, aby wynik był w zmiennej, możesz użyć odwrotnych apostrofów:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
Nate W.
źródło
1

Jako alternatywa dla istniejących odpowiedzi: użyj, whilejeśli nazwy plików mogą zawierać spacje, takie jak:

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

Jak napisałem, różnica jest istotna tylko wtedy, gdy w nazwach plików trzeba się spodziewać spacji.

NB: jedyne wbudowane rzeczy nie dotyczą wyjścia, ale statusu ostatniego polecenia.

0xC0000022L
źródło
1

możesz użyć !!: 1. Przykład:

~]$ 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
rkm
źródło
Ta notacja pobiera numerowany argument z polecenia: Zobacz dokumentację „$ {parameter: offset}” tutaj: gnu.org/software/bash/manual/html_node/… . Zobacz także tutaj, aby uzyskać więcej przykładów: howtogeek.com/howto/44997/…
Alexander Bird
1

Miałem podobną potrzebę, w której chciałem wykorzystać wyjście ostatniego polecenia do następnego. Podobnie jak | (rura). na przykład

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

na coś takiego -

$ which gradle |: ls -altr {}

Rozwiązanie: utworzono tę niestandardową rurę. Naprawdę proste, używając xargs -

$ alias :='xargs -I{}'

W zasadzie nic, tworząc krótkie rozdanie dla xargs, działa jak urok i jest naprawdę przydatne. Po prostu dodaję alias w pliku .bash_profile.

eXc
źródło
1

Można to zrobić za pomocą magii deskryptorów plików i lastpipeopcji powłoki.

Trzeba to zrobić za pomocą skryptu - opcja „lastpipe” nie będzie działać w trybie interaktywnym.

Oto skrypt, z którym testowałem:

$ 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"

To, co tu robię, to:

  1. ustawienie opcji powłoki shopt -s lastpipe. Bez tego nie zadziała, ponieważ utracisz deskryptor pliku.

  2. upewniając się, że mój stderr również zostanie przechwycony 2>&1

  3. przesyłanie danych wyjściowych do funkcji, aby można było odwołać się do deskryptora pliku stdin.

  4. ustawienie zmiennej poprzez pobranie zawartości pliku /proc/self/fd/0 deskryptora pliku, którym jest stdin.

Używam tego do przechwytywania błędów w skrypcie, więc jeśli wystąpi problem z poleceniem, mogę przerwać przetwarzanie skryptu i od razu wyjść.

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

W ten sposób mogę dodać 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msgkażde polecenie, które chcę sprawdzić.

Teraz możesz cieszyć się swoją produkcją!

montjoy
źródło
0

Nie jest to rozwiązanie ściśle bash, ale możesz użyć potoku z sedem, aby uzyskać wynik ostatniego wiersza poprzednich poleceń.

Najpierw zobaczmy, co mam w folderze „a”

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

Wtedy twój przykład z ls i cd zamieniłby się w sed & piping w coś takiego:

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

Tak więc prawdziwa magia dzieje się z sedem, potokujesz wszystko, co kiedykolwiek wyjście z polecenia, do seda, a sed wypisuje ostatni wiersz, którego możesz użyć jako parametru z tylnymi tikami. Możesz też połączyć to z xargs. („człowiek xargs” w powłoce to twój przyjaciel)

rasjani
źródło
0

Powłoka nie ma specjalnych symboli podobnych do perla, które przechowują wynik echa ostatniego polecenia.

Naucz się używać symbolu potoku w awk.

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

W powyższym przykładzie możesz zrobić:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'
ascotan
źródło
0
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

to jeszcze jeden sposób, choć brudny.

Kevin
źródło
odnaleźć . -name foo.txt 1> tmpfile && mv `cat tmpfile` ścieżka / do / jakiegoś / katalogu && rm tmpfile
Kevin
0

Uważam, że pamiętam, aby potokować wyjście moich poleceń do określonego pliku, aby być nieco irytujące, moim rozwiązaniem jest funkcja w moim, .bash_profilektóra przechwytuje dane wyjściowe w pliku i zwraca wynik, kiedy go potrzebujesz.

Zaletą tego jest to, że nie musisz ponownie uruchamiać całego polecenia (podczas używania findlub innych długotrwałych poleceń, które mogą być krytyczne)

Wystarczająco proste, wklej to do swojego .bash_profile :

Scenariusz

# 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 }

Stosowanie

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

$ res
/path/to/filename

W tym momencie staram się po prostu dodawać | catchna końcu wszystkie moje polecenia, ponieważ nie ma to żadnych kosztów i oszczędza mi konieczności ponownego uruchamiania poleceń, których zakończenie zajmuje dużo czasu.

Ponadto, jeśli chcesz otworzyć plik wyjściowy w edytorze tekstu, możesz to zrobić:

# vim or whatever your favorite text editor is
$ vim <(res)
Connor
źródło