Menggunakan kembali output dari perintah terakhir di Bash

Jawaban:

184

Anda dapat menggunakan $(!!) untuk menghitung ulang (tidak menggunakan kembali) output dari perintah terakhir.

The !!sendiri menjalankan perintah terakhir.

$ echo pierre
pierre
$ echo my name is $(!!)
echo my name is $(echo pierre)
my name is pierre
ling
sumber
9
Anda dapat menyelamatkan diri dari mengetik $(your_cmd)dengan menggunakan backticks: `your_cmd`Menurut manual Gnu Bash, keduanya secara fungsional identik. Tentu saja ini juga tunduk pada peringatan yang diangkat oleh @memecs
Dalam prakteknya, output dari perintah adalah deterministik sehingga saya tidak akan merasa sakit hati untuk mengkomputasi ulang Ini tentu berguna ketika Anda ingin melakukan sesuatu seperti di git diff $(!!)mana perintah sebelumnya adalah finddoa.
Sridhar Sarnobat
Rupanya ada alasan bagus untuk digunakan $()daripada backticks. Tapi mungkin tidak ada ruginya tidur. github.com/koalaman/shellcheck/wiki/SC2006
Michael Crenshaw
1
@ user2065875 meskipun backticks masih berfungsi di Bash, mereka tidak digunakan lagi $().
kurczynski
87

Jawabannya adalah tidak. Bash tidak mengalokasikan output apa pun ke parameter apa pun atau blok apa pun di memorinya. Selain itu, Anda hanya diizinkan untuk mengakses Bash dengan operasi antarmuka yang diizinkan. Data pribadi Bash tidak dapat diakses kecuali jika Anda meretasnya.

konsolebox
sumber
2
Apakah mungkin untuk memiliki beberapa fungsi 'middleware' bash kustom yang berjalan sebelum setiap perintah, di mana yang dilakukannya hanyalah menyimpan output yang dihasilkan ke sesuatu yang bertindak sebagai clipboard internal (katakanlah, BASH_CLIPBOARD)?
Pat Needham
@ PatNeedham Tidak yakin. Periksa modul yang dapat dimuat. Lihat apakah Anda dapat menulisnya.
konsolebox
11

Salah satu cara melakukannya adalah dengan menggunakan trap DEBUG:

f() { bash -c "$BASH_COMMAND" >& /tmp/out.log; }
trap 'f' DEBUG

Sekarang stdout dan stderr perintah yang baru saja dieksekusi akan tersedia di /tmp/out.log

Satunya downside adalah bahwa ia akan menjalankan perintah dua kali: sekali untuk mengarahkan output dan kesalahan ke /tmp/out.logdan sekali secara normal. Mungkin ada beberapa cara untuk mencegah perilaku ini juga.

anubhava
sumber
10
jadi ketika saya mengetik rm -rf * && cd .., tidak hanya direktori saat ini, tetapi juga direktori induk akan dihapus ?? pendekatan ini tampaknya sangat berbahaya bagi saya
phil294
1
Sekarang bagaimana saya membatalkan tindakan ini?
Kartik Chauhan
5
@KartikChauhan: Lakukan sajatrap '' DEBUG
anubhava
7

Jika Anda menggunakan mac, dan tidak keberatan menyimpan output Anda di clipboard alih-alih menulis ke variabel, Anda dapat menggunakan pbcopy dan pbpaste sebagai solusinya.

Sebagai contoh, alih-alih melakukan ini untuk menemukan file dan berbeda isinya dengan file lain:

$ find app -name 'one.php' 
/var/bar/app/one.php

$ diff /var/bar/app/one.php /var/bar/two.php

Anda bisa melakukan ini:

$ find app -name 'one.php' | pbcopy
$ diff $(pbpaste) /var/bar/two.php

Tali /var/bar/app/one.phpada di clipboard ketika Anda menjalankan perintah pertama.

By the way, pb di pbcopydan pbpasteberdiri untuk karton, sinonim untuk clipboard.

Raghu Dodda
sumber
1
di linux $ find app -name 'one.php' | xclip $ diff $(xclip -o) /var/bar/two.php
si-mikey
menarik, saya tidak pernah berpikir untuk menggunakan pbpastebersama dengan substitusi perintah.
Sridhar Sarnobat
Bagaimana jika saya melakukan yang pertama dan kemudian menyadari itu tidak efisien, bagaimana saya menghemat waktu komputasi tanpa menjalankan kembali dengan opsi kedua?
NelsonGon
4

Terinspirasi oleh jawaban anubhava, yang menurut saya sebenarnya tidak dapat diterima karena menjalankan setiap perintah dua kali.

save_output() { 
   exec 1>&3 
   { [ -f /tmp/current ] && mv /tmp/current /tmp/last; }
   exec > >(tee /tmp/current)
}

exec 3>&1
trap save_output DEBUG

Dengan cara ini output dari perintah terakhir ada di / tmp / last dan perintah itu tidak dipanggil dua kali.

olivecoder
sumber
1
Satu kekurangan yang saya temukan pada hal ini adalah bahwa tidak ada perintah Anda yang berpikir bahwa mereka menjalankan tty lagi, yang berarti Anda perlu perhatian khusus untuk mendapatkan kode warna yang berfungsi untuk utilitas seperti grepatau git diff+1 tetap
Marty Neal
2

Seperti yang dikatakan konsolebox, Anda harus meretas bash itu sendiri. Sini adalah contoh yang cukup bagus tentang bagaimana seseorang dapat mencapai ini. Repositori stderred (sebenarnya dimaksudkan untuk mewarnai stdout) memberikan instruksi tentang cara membuatnya.

Aku mencoba memberikannya: Mendefinisikan beberapa di dalam file descriptor baru .bashrcseperti

exec 41>/tmp/my_console_log

(angka berubah-ubah) dan memodifikasi stderred.csesuai sehingga konten juga ditulis ke fd 41. Ini semacam bekerja, tetapi berisi banyak byte NUL, format aneh dan pada dasarnya adalah data biner, tidak dapat dibaca. Mungkin seseorang dengan pemahaman C yang baik bisa mencobanya.

Jika demikian, semua yang diperlukan untuk mendapatkan garis cetakan terakhir adalah tail -n 1 [logfile].

phil294
sumber
1

Ya, mengapa mengetik garis ekstra setiap kali setuju. Anda dapat mengarahkan kembali ke input, tetapi output ke input (1> & 0) tidak. Anda juga tidak akan ingin menulis fungsi lagi dan lagi di setiap file untuk hal yang sama.

Jika Anda tidak mengekspor file di mana saja dan bermaksud menggunakannya secara lokal, Anda dapat meminta Terminal mengatur deklarasi fungsi. Anda harus menambahkan fungsi dalam ~/.bashrcfile atau ~/.profilefile. Dalam kasus kedua, Anda harus mengaktifkan Run command as login shelldari Edit>Preferences>yourProfile>Command.

Buat fungsi sederhana, katakan:

get_prev()
{
    # option 1: create an executable with the command(s) and run it
    echo $* > /tmp/exe
    bash /tmp/exe > /tmp/out

    # option 2: if your command is single command (no-pipe, no semi-colons), still it may not run correct in some exceptions
    #echo `$*` > /tmp/out

    # return the command(s) outputs line by line
    IFS=$(echo -en "\n\b")
    arr=()
    exec 3</tmp/out
    while read -u 3 -r line
    do
        arr+=($line)
        echo $line
    done
    exec 3<&-
}

Dalam skrip utama:

#run your command:
cmd="echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"
get_prev $cmd
#or simply
get_prev "echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"

Bash menyimpan variabel bahkan di luar ruang lingkup sebelumnya:

#get previous command outputs in arr
for((i=0; i<${#arr[@]}; i++)); do echo ${arr[i]}; done
Himanshu Tanwar
sumber
1

Solusi yang Sangat Sederhana

Salah satu yang saya gunakan selama bertahun-tahun.

Skrip (tambahkan ke Anda .bashrcatau .bash_profile)

# capture the output of a command so it can be retrieved with ret
cap () { tee /tmp/capture.out}

# return the output of the most recent command that was captured by cap
ret () { cat /tmp/capture.out }

Pemakaian

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

$ ret
/path/to/filename

Saya cenderung menambahkan | cappada akhir semua perintah saya. Dengan cara ini ketika saya menemukan saya ingin melakukan pemrosesan teks pada output dari perintah yang berjalan lambat saya selalu dapat mengambilnya dengan res.

Connor
sumber
Apa gunanya cat -di cap () { cat - | tee /tmp/capture.out}sini? Apakah cap () { tee /tmp/capture.out }ada cegukan yang hilang?
Watson
1
@Watson poin bagus! Saya tidak tahu mengapa saya memilikinya di sana, kemungkinan kebiasaan. Haruskah saya menambahkan cat - | cat - | cat -terlebih dahulu hanya untuk membuatnya lebih menarik? 😉 Saya akan memperbaikinya setelah saya menguji untuk memastikannya benar-benar spandrel
Connor
0

Tidak yakin untuk apa Anda membutuhkannya, jadi jawaban ini mungkin tidak relevan. Anda selalu dapat menyimpan output dari suatu perintah:netstat >> output.txt tapi saya tidak berpikir itu yang Anda cari.

Tentu saja ada opsi pemrograman; Anda bisa mendapatkan program untuk membaca file teks di atas setelah perintah itu dijalankan dan mengaitkannya dengan variabel, dan di Ruby, bahasa pilihan saya, Anda dapat membuat variabel dari output perintah menggunakan 'backticks':

output = `ls`                       #(this is a comment) create variable out of command

if output.include? "Downloads"      #if statement to see if command includes 'Downloads' folder
print "there appears to be a folder named downloads in this directory."
else
print "there is no directory called downloads in this file."
end

Stick ini dalam file .rb dan jalankan: ruby file.rbdan itu akan membuat variabel dari perintah dan memungkinkan Anda untuk memanipulasinya.

pengguna4493605
sumber
9
"Tidak yakin persis apa yang Anda butuhkan untuk ini" Ya, katakan saja Anda (maksud saya, saya ) hanya menjalankan skrip berdurasi panjang, ingin mencari output, dan dengan bodoh lupa mengarahkan ulang output sebelum mengeksekusi. Sekarang saya ingin mencoba untuk memperbaiki kebodohan saya tanpa menjalankan kembali skrip.
jscs
0

Saya punya ide bahwa saya tidak punya waktu untuk mencoba menerapkan segera.

Tetapi bagaimana jika Anda melakukan sesuatu seperti berikut:

$ MY_HISTORY_FILE = `get_temp_filename`
$ MY_HISTORY_FILE=$MY_HISTORY_FILE bash -i 2>&1 | tee $MY_HISTORY_FILE
$ some_command
$ cat $MY_HISTORY_FILE
$ # ^You'll want to filter that down in practice!

Mungkin ada masalah dengan buffering IO. Juga file mungkin menjadi terlalu besar. Seseorang harus menemukan solusi untuk masalah-masalah ini.

William Navarre
sumber
0

Saya pikir menggunakan perintah skrip mungkin membantu. Sesuatu seperti,

  1. skrip -c bash -qf fifo_pid

  2. Menggunakan fitur bash untuk mengatur setelah parsing.

sudhakar
sumber
0

Jika Anda tidak ingin menghitung ulang perintah sebelumnya, Anda dapat membuat makro yang memindai buffer terminal saat ini, mencoba menebak output -supposed- dari perintah terakhir, menyalinnya ke clipboard dan akhirnya mengetiknya ke terminal.

Ini dapat digunakan untuk perintah sederhana yang mengembalikan satu baris output (diuji pada Ubuntu 18.04 with gnome-terminal).

Menginstal alat berikut: xdootool, xclip,ruby

Di gnome-terminalbuka Preferences -> Shortcuts -> Select alldan setel ke Ctrl+shift+a.

Buat skrip ruby ​​berikut:

cat >${HOME}/parse.rb <<EOF
#!/usr/bin/ruby
stdin = STDIN.read
d = stdin.split(/\n/)
e = d.reverse
f = e.drop_while { |item| item == "" }
g = f.drop_while { |item| item.start_with? "${USER}@" }
h = g[0] 
print h
EOF

Dalam pengaturan keyboard, tambahkan pintasan keyboard berikut:

bash -c '/bin/sleep 0.3 ; xdotool key ctrl+shift+a ; xdotool key ctrl+shift+c ; ( (xclip -out | ${HOME}/parse.rb ) > /tmp/clipboard ) ; (cat /tmp/clipboard | xclip -sel clip ) ; xdotool key ctrl+shift+v '

Pintasan di atas:

  • menyalin buffer terminal saat ini ke clipboard
  • mengekstrak output dari perintah terakhir (hanya satu baris)
  • mengetikkannya ke terminal saat ini
Giuliano
sumber