Secara otomatis menangkap output dari perintah terakhir ke dalam variabel menggunakan Bash?

139

Saya ingin dapat menggunakan hasil dari perintah yang dieksekusi terakhir di perintah berikutnya. Sebagai contoh,

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

Sekarang katakanlah saya ingin dapat membuka file dalam editor, atau menghapusnya, atau melakukan sesuatu yang lain dengan itu, misalnya

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

Bagaimana saya bisa melakukannya? Mungkin menggunakan beberapa variabel bash?

Memperbarui:

Untuk memperjelas, saya tidak ingin menetapkan hal-hal secara manual. Apa yang saya kejar adalah sesuatu seperti variabel bash bawaan, misalnya

ls /tmp
cd $_

$_memegang argumen terakhir dari perintah sebelumnya. Saya menginginkan sesuatu yang serupa, tetapi dengan output dari perintah terakhir.

Pembaruan terakhir:

Jawaban Seth telah bekerja dengan cukup baik. Beberapa hal yang perlu diingat:

  • jangan lupa touch /tmp/xketika mencoba solusi untuk pertama kalinya
  • hasilnya hanya akan disimpan jika kode keluar perintah terakhir berhasil
armandino
sumber
Setelah melihat hasil edit Anda, saya berpikir untuk menghapus jawaban saya. Saya ingin tahu apakah ada sesuatu yang built-in yang Anda cari.
taskinoor
Saya tidak dapat menemukan apa pun yang ada di dalamnya. Saya bertanya-tanya apakah mungkin untuk mengimplementasikannya .. mungkin melalui .bahsrc? Saya pikir itu akan menjadi fitur yang sangat berguna.
armandino
Saya khawatir yang dapat Anda lakukan hanyalah mengarahkan output ke file atau pipa atau menangkapnya, jika tidak maka tidak akan disimpan.
bandi
3
Anda tidak dapat melakukannya tanpa kerja sama dari shell dan terminal, dan mereka umumnya tidak bekerja sama. Lihat juga Bagaimana cara menggunakan kembali output terakhir dari baris perintah? dan Menggunakan teks dari output perintah sebelumnya di Unix Stack Exchange .
Gilles 'SANGAT berhenti menjadi jahat'
6
Salah satu alasan utama mengapa output dari perintah tidak ditangkap adalah karena output dapat besar secara arbitrer - banyak megabyte dalam satu waktu. Memang, tidak selalu sebesar itu, tetapi keluaran besar menyebabkan masalah.
Jonathan Leffler

Jawaban:

71

Ini adalah solusi yang benar-benar gila, tetapi tampaknya sebagian besar berfungsi beberapa waktu. Selama pengujian, saya mencatat kadang-kadang tidak bekerja dengan baik ketika mendapatkan ^Cdi baris perintah, meskipun saya sedikit mengubah sedikit untuk berperilaku sedikit lebih baik.

Peretasan ini hanya peretasan mode interaktif, dan saya cukup yakin bahwa saya tidak akan merekomendasikannya kepada siapa pun. Perintah latar belakang cenderung menyebabkan perilaku yang bahkan lebih tidak terdefinisi daripada normal. Jawaban lainnya adalah cara yang lebih baik untuk mendapatkan hasil secara terprogram.


Yang sedang berkata, inilah "solusi":

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

Setel variabel lingkungan bash ini dan berikan perintah yang diinginkan. $LASTbiasanya akan memiliki output yang Anda cari:

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
sumber
1
@armandino: Ini tentu saja menyebabkan program yang berharap untuk berinteraksi dengan terminal pada standard-out tidak berfungsi seperti yang diharapkan (lebih / kurang) atau menyimpan hal-hal aneh dalam $ LAST (emacs). Tapi saya pikir ini tentang sebaik yang akan Anda dapatkan. Satu-satunya pilihan lain adalah menggunakan skrip (ketik) untuk menyimpan salinan SEMUA yang ada pada file dan kemudian menggunakan PROMPT_COMMAND untuk memeriksa perubahan sejak PROMPT_COMMAND terakhir. Ini akan mencakup hal-hal yang tidak Anda inginkan. Saya cukup yakin Anda tidak akan menemukan apa pun yang lebih dekat dengan apa yang Anda inginkan dengan ini.
Seth Robertson
2
Untuk melangkah lebih jauh: 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)'yang menyediakan keduanya $lastdan $lasterr.
ℝaphink
@cdosborn: man secara default mengirimkan output melalui pager. Seperti komentar saya sebelumnya mengatakan: "program berharap untuk berinteraksi dengan terminal pada standard-out agar tidak berfungsi seperti yang diharapkan (lebih / kurang)". lebih & kurang adalah pager.
Seth Robertson
Saya mendapatkan:No such file or directory
Francisco Corrales Morales
@Raphink Saya hanya ingin menunjukkan koreksi: saran Anda harus diakhiri 2> >(tee /tmp/lasterr 1>&2)karena output standar teeharus diarahkan kembali ke kesalahan standar.
Pelukan
90

Saya tidak tahu ada variabel yang melakukan ini secara otomatis . Untuk melakukan sesuatu selain dari hanya menyalin hasil, Anda dapat menjalankan kembali apa pun yang baru saja Anda lakukan, misalnya

vim $(!!)

Dimana !! ekspansi sejarah berarti 'perintah sebelumnya'.

Jika Anda berharap akan ada nama file tunggal dengan spasi atau karakter lain di dalamnya yang dapat mencegah penguraian argumen yang tepat, kutip hasilnya ( vim "$(!!)"). Membiarkannya tanpa tanda kutip akan memungkinkan banyak file dibuka sekaligus selama tidak menyertakan spasi atau token penguraian shell lainnya.

Daenyth
sumber
1
Copy-paste adalah apa yang biasanya saya lakukan dalam kasus seperti itu, juga karena Anda biasanya hanya membutuhkan sebagian dari output perintah. Saya terkejut Anda adalah orang pertama yang menyebutkannya.
Bruno De Fraine
4
Anda dapat melakukan hal yang sama dengan menekan tombol lebih sedikit dengan:vim `!!`
psmears
Untuk melihat perbedaan dari jawaban yang diterima, jalankan date "+%N"dan kemudianecho $(!!)
Marinos An
20

Bash adalah jenis bahasa yang jelek. Ya, Anda dapat menetapkan output ke variabel

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

Tapi lebih baik berharap yang tersulit kamu itu find hanya mengembalikan satu hasil dan bahwa hasil itu tidak memiliki karakter "aneh" di dalamnya, seperti carriage return atau umpan baris, karena mereka akan diam-diam dimodifikasi ketika ditugaskan ke variabel Bash.

Tapi lebih baik hati-hati mengutip variabel Anda dengan benar saat menggunakannya!

Lebih baik untuk bertindak pada file secara langsung, misalnya dengan find's -execdir(konsultasikan manual).

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

atau

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'
ribby
sumber
5
Mereka tidak diam - diam dimodifikasi ketika Anda menetapkan, mereka dimodifikasi ketika Anda menggema! Anda hanya perlu melakukannya echo "${MY_VAR}"untuk melihat hal ini.
paxdiablo
13

Ada lebih dari satu cara untuk melakukan ini. Salah satu caranya adalah dengan menggunakan v=$(command)yang akan menugaskan output perintah v. Sebagai contoh:

v=$(date)
echo $v

Dan Anda dapat menggunakan backquote juga.

v=`date`
echo $v

Dari Bash Beginners Guide ,

Ketika bentuk subtitusi backquoted gaya lama digunakan, backslash mempertahankan makna literalnya kecuali bila diikuti oleh "$", "` ", atau" \ ". Backticks pertama yang tidak diawali dengan backslash mengakhiri substitusi perintah. Saat menggunakan formulir "$ (PERINTAH)", semua karakter di antara tanda kurung membuat perintah; tidak ada yang diperlakukan secara khusus.

EDIT: Setelah diedit dalam pertanyaan, tampaknya ini bukan hal yang dicari OP. Sejauh yang saya tahu, tidak ada variabel khusus seperti $_untuk output dari perintah terakhir.

taskinoor
sumber
11

Cukup mudah. Gunakan kutipan balik:

var=`find . -name foo.txt`

Dan kemudian Anda dapat menggunakannya kapan saja di masa depan

echo $var
mv $var /somewhere
Wes Hardaker
sumber
11

Disclamers:

  • Jawaban ini terlambat setengah tahun: D
  • Saya pengguna tmux yang berat
  • Anda harus menjalankan shell Anda di tmux agar ini berfungsi

Saat menjalankan shell interaktif di tmux, Anda dapat dengan mudah mengakses data yang saat ini ditampilkan di terminal. Mari kita lihat beberapa perintah menarik:

  • tmux capture-pane : ini menyalin data yang ditampilkan ke salah satu buffer internal tmux. Itu dapat menyalin riwayat yang saat ini tidak terlihat, tetapi kami tidak tertarik dengan itu sekarang
  • tmux list-buffer : ini menampilkan info tentang buffer yang ditangkap. Yang terbaru akan memiliki angka 0.
  • tmux show-buffer -b (buffer num) : ini mencetak isi buffer yang diberikan pada terminal
  • tmux paste-buffer -b (buffer num) : ini menempelkan isi buffer yang diberikan sebagai input

Ya, ini memberi kami banyak kemungkinan sekarang :) Sedangkan bagi saya, saya membuat alias sederhana: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1"dan sekarang setiap kali saya perlu mengakses baris terakhir yang saya gunakan $(L)untuk mendapatkannya.

Ini tidak tergantung pada aliran output yang digunakan program (baik stdin atau stderr), metode pencetakan (ncurses, dll.) Dan kode keluar program - data hanya perlu ditampilkan.

Wiesław Herr
sumber
Hei, terima kasih atas tip tmux ini. Saya pada dasarnya mencari hal yang sama dengan OP, menemukan komentar Anda, dan membuat kode skrip shell untuk membiarkan Anda memilih dan menempel bagian dari output dari perintah sebelumnya menggunakan perintah tmux yang Anda sebutkan dan gerakan Vim: github.com/ bgribble / lw
Bill Gribble
9

Saya pikir Anda mungkin bisa meretas solusi yang melibatkan pengaturan shell Anda ke skrip yang berisi:

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

Kemudian jika Anda mengatur $PROMPT_COMMANDuntuk mengeluarkan pembatas, Anda dapat menulis fungsi pembantu (mungkin disebut _) yang memberi Anda potongan terakhir dari log itu, sehingga Anda dapat menggunakannya seperti:

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

Anda dapat mengatur alias berikut di profil bash Anda:

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

Kemudian, dengan mengetikkan 's' setelah perintah arbitrer, Anda dapat menyimpan hasilnya ke variabel shell 'itu'.

Jadi contoh penggunaannya adalah:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'
Joe Tallett
sumber
Terima kasih! Inilah yang saya butuhkan untuk mengimplementasikan grabfungsi saya yang menyalin baris ke-n dari perintah terakhir ke clipboard gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq
6

Saya baru saja menyaring bashfungsi ini dari saran di sini:

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

Kemudian, Anda cukup melakukan:

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

Update : seorang pengguna anonim disarankan untuk mengganti echodengan printf '%s\n'yang memiliki keuntungan bahwa itu tidak pilihan proses seperti -edalam teks meraih. Jadi, jika Anda mengharapkan atau mengalami kekhasan seperti itu, pertimbangkan saran ini. Pilihan lain adalah menggunakan cat <<<$grab.

Tilman Vogel
sumber
1
Ok, sebenarnya ini bukan jawaban karena, bagian "otomatis" benar-benar hilang.
Tilman Vogel
Bisakah Anda menjelaskan cat <<<$grabpilihannya?
leoj
1
Mengutip dari man bash: "Sini Strings: Varian dari dokumen di sini, formatnya adalah: <<<wordKata tersebut diperluas dan dipasok ke perintah pada input standarnya." Jadi, nilai $grabdiumpankan ke catstdin, dan catmasukkan saja kembali ke stdout.
Tilman Vogel
5

Dengan mengatakan "Saya ingin dapat menggunakan hasil dari perintah yang dieksekusi terakhir dalam perintah berikutnya", saya berasumsi - maksud Anda adalah hasil dari setiap perintah, bukan hanya menemukan.

Jika itu masalahnya - xargs adalah apa yang Anda cari.

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

ATAU jika Anda tertarik untuk melihat hasilnya terlebih dahulu:

find . -name foo.txt -print0

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

Perintah ini berkaitan dengan banyak file dan berfungsi seperti jimat bahkan jika path dan / atau nama file mengandung spasi.

Perhatikan bagian mv {} / some / new / location / {} dari perintah. Perintah ini dibuat dan dieksekusi untuk setiap baris yang dicetak oleh perintah sebelumnya. Di sini baris yang dicetak oleh perintah sebelumnya diganti dengan {} .

Kutipan dari halaman manual xargs:

xargs - membangun dan mengeksekusi baris perintah dari input standar

Untuk detail lebih lanjut lihat halaman manual: man xargs

ssapkota
sumber
5

Tangkap output dengan backticks:

output=`program arguments`
echo $output
emacs $output
bandi
sumber
4

Saya biasanya melakukan apa yang disarankan orang lain di sini ... tanpa tugas:

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

Anda bisa menjadi pelamun jika Anda suka:

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

Jika yang Anda inginkan adalah menjalankan kembali perintah terakhir Anda dan mendapatkan output, variabel bash sederhana akan berfungsi:

LAST=`!!`

Jadi, Anda dapat menjalankan perintah pada output dengan:

yourCommand $LAST

Ini akan menelurkan proses baru dan jalankan kembali perintah Anda, kemudian memberi Anda output. Kedengarannya seperti apa yang benar-benar Anda inginkan akan menjadi file bash history untuk output perintah Ini berarti Anda harus menangkap output yang dikirimkan bash ke terminal Anda. Anda bisa menulis sesuatu untuk menonton / dev atau / proc yang diperlukan, tapi itu berantakan. Anda juga bisa membuat "pipa khusus" antara istilah Anda dan bash dengan perintah tee di tengah yang mengarahkan ke file output Anda.

Namun keduanya merupakan solusi yang bersifat meretas. Saya pikir yang terbaik adalah terminator yang merupakan terminal yang lebih modern dengan output logging. Cukup periksa file log Anda untuk hasil dari perintah terakhir. Variabel bash mirip dengan di atas akan membuat ini lebih sederhana.

Spencer Rathbun
sumber
1

Inilah salah satu cara untuk melakukannya setelah Anda menjalankan perintah dan memutuskan bahwa Anda ingin menyimpan hasilnya dalam sebuah variabel:

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

Atau jika Anda tahu sebelumnya bahwa Anda ingin hasil dalam variabel, Anda dapat menggunakan backticks:

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

Sebagai alternatif dari jawaban yang ada: Gunakan whilejika nama file Anda dapat berisi ruang kosong seperti ini:

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

Seperti yang saya tulis, perbedaannya hanya relevan jika Anda harus mengharapkan kosong dalam nama file.

NB: satu-satunya hal bawaan bukan tentang output tetapi tentang status perintah terakhir.

0xC0000022L
sumber
1

Anda dapat menggunakan !!: 1. Contoh:

~]$ 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
sumber
Notasi ini mengambil argumen bernomor dari sebuah perintah: Lihat dokumentasi untuk "$ {parameter: offset}" di sini: gnu.org/software/bash/manual/html_node/… . Lihat juga di sini untuk contoh lebih lanjut: howtogeek.com/howto/44997/…
Alexander Bird
1

Saya memiliki kebutuhan yang sama, di mana saya ingin menggunakan output dari perintah terakhir ke yang berikutnya. Mirip seperti | (pipa). misalnya

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

untuk sesuatu seperti -

$ which gradle |: ls -altr {}

Solusi: Membuat pipa khusus ini. Sangat sederhana, menggunakan xargs -

$ alias :='xargs -I{}'

Pada dasarnya bukan apa-apa dengan membuat tulisan tangan pendek untuk xargs, itu berfungsi seperti pesona, dan sangat berguna. Saya hanya menambahkan alias dalam file .bash_profile.

eXc
sumber
1

Ini dapat dilakukan dengan menggunakan keajaiban deskriptor file dan lastpipeopsi shell.

Itu harus dilakukan dengan skrip - opsi "lastpipe" tidak akan berfungsi dalam mode interaktif.

Berikut skrip yang telah saya uji:

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

Yang saya lakukan di sini adalah:

  1. mengatur opsi shell shopt -s lastpipe. Ini tidak akan berfungsi tanpa ini karena Anda akan kehilangan deskriptor file.

  2. memastikan stderr saya juga ditangkap 2>&1

  3. piping output ke fungsi sehingga deskriptor file stdin dapat direferensikan.

  4. pengaturan variabel dengan mendapatkan isi /proc/self/fd/0deskriptor file, yaitu stdin.

Saya menggunakan ini untuk menangkap kesalahan dalam skrip jadi jika ada masalah dengan perintah saya bisa berhenti memproses skrip dan keluar segera.

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

Dengan cara ini saya dapat menambahkan di 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msgbalik setiap perintah yang ingin saya periksa.

Sekarang Anda dapat menikmati output Anda!

montjoy
sumber
0

Ini bukan semata-mata solusi bash tetapi Anda bisa menggunakan piping dengan sed untuk mendapatkan baris terakhir dari perintah keluaran sebelumnya.

Pertama mari kita lihat apa yang ada di folder "a"

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

Kemudian, contoh Anda dengan ls dan cd akan berubah menjadi sed & piping menjadi sesuatu seperti ini:

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

Jadi, keajaiban yang sebenarnya terjadi dengan sed, Anda menyalurkan output apa pun dari perintah yang pernah ada ke sed dan mencetak baris terakhir yang dapat Anda gunakan sebagai parameter dengan kutu belakang. Atau Anda dapat menggabungkannya ke xargs juga. ("man xargs" di shell adalah teman Anda)

rasjani
sumber
0

Shell tidak memiliki simbol khusus seperti perl yang menyimpan hasil gema dari perintah terakhir.

Belajar menggunakan simbol pipa dengan awk.

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

Pada contoh di atas Anda bisa melakukan:

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

adalah cara lain, meskipun kotor.

Kevin
sumber
Temukan . -nama foo.txt 1> tmpfile && mv `cat tmpfile` path / ke / some / dir && rm tmpfile
Kevin
0

Saya ingat untuk mem-pipe output dari perintah saya ke file tertentu menjadi sedikit mengganggu, solusi saya adalah fungsi di saya .bash_profileyang menangkap output dalam file dan mengembalikan hasilnya ketika Anda membutuhkannya.

Keuntungan dengan yang satu ini adalah Anda tidak perlu menjalankan ulang seluruh perintah (saat menggunakan findatau perintah jangka panjang lainnya yang bisa menjadi sangat penting)

Cukup sederhana, rekatkan ini di .bash_profile:

Naskah

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

Pemakaian

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

$ res
/path/to/filename

Pada titik ini, saya cenderung hanya menambahkan | catchke akhir dari semua perintah saya, karena tidak ada biaya untuk melakukannya dan menghemat saya harus menjalankan kembali perintah yang membutuhkan waktu lama untuk menyelesaikannya.

Juga, jika Anda ingin membuka output file dalam editor teks, Anda dapat melakukan ini:

# vim or whatever your favorite text editor is
$ vim <(res)
Connor
sumber