Apa gunanya variabel $ BASH_COMMAND?

25

Menurut manual Bash , variabel lingkungan BASH_COMMANDberisi

Perintah saat ini sedang dieksekusi atau akan dieksekusi, kecuali shell mengeksekusi perintah sebagai hasil jebakan, dalam hal ini adalah perintah yang dieksekusi pada saat jebakan.

Mengambil kasus sudut perangkap itu, jika saya mengerti dengan benar ini berarti bahwa ketika saya menjalankan perintah, variabel BASH_COMMANDberisi perintah itu. Tidak sepenuhnya jelas apakah variabel itu tidak disetel setelah eksekusi perintah (yaitu, hanya tersedia ketika perintah sedang berjalan, tetapi tidak setelah), meskipun orang mungkin berpendapat bahwa karena itu adalah "perintah saat ini sedang dieksekusi atau akan dieksekusi" , itu bukan perintah yang baru saja dieksekusi.

Tapi mari kita periksa:

$ set | grep BASH_COMMAND=
$ 

Kosong. Saya akan berharap untuk melihat BASH_COMMAND='set | grep BASH_COMMAND='atau mungkin adil BASH_COMMAND='set', tetapi kosong mengejutkan saya.

Mari kita coba yang lain:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Itu masuk akal. Saya menjalankan perintah echo $BASH_COMMANDdan variabel tersebut BASH_COMMANDberisi string echo $BASH_COMMAND. Mengapa ini bekerja saat ini, tetapi tidak sebelumnya?

Mari kita lakukan setlagi:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Jadi, tunggu. Hal itu ditetapkan ketika saya dieksekusi bahwa echoperintah, dan itu tidak diset setelah itu. Tetapi ketika saya dieksekusi setlagi, BASH_COMMAND tidak diatur ke setperintah. Tidak peduli seberapa sering saya menjalankan setperintah di sini, hasilnya tetap sama. Jadi, apakah variabel ditetapkan ketika menjalankan echo, tetapi tidak ketika menjalankan set? Ayo lihat.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Apa? Jadi variabel ditetapkan ketika saya dieksekusi echo $BASH_COMMAND, tetapi tidak ketika saya dieksekusi echo Hello AskUbuntu? Di mana bedanya sekarang? Apakah variabel hanya disetel ketika perintah saat ini sendiri benar-benar memaksa shell untuk mengevaluasi variabel? Mari kita coba sesuatu yang berbeda. Mungkin beberapa perintah eksternal kali ini, bukan bash builtin, untuk suatu perubahan.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Hmm, ok ... lagi, variabelnya sudah diset. Jadi, apakah tebakan saya saat ini benar? Apakah variabel hanya ditetapkan ketika harus dievaluasi? Mengapa? Mengapa? Untuk alasan kinerja? Mari kita coba sekali lagi. Kami akan mencoba grep untuk $BASH_COMMANDdalam file, dan karena itu $BASH_COMMANDharus mengandung grepperintah, grepharus grep untuk grepperintah itu (yaitu, untuk dirinya sendiri). jadi mari kita buat file yang sesuai:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ok, menarik. Perintah grep $BASH_COMMAND tmpmenjadi diperluas ke grep grep $BASH_COMMAND tmp tmp(variabel akan diperluas hanya sekali, tentu saja), dan jadi saya mengerti grep, sekali dalam file $BASH_COMMANDyang tidak ada, dan dua kali dalam file tmp.

T1: Apakah asumsi saya saat ini benar bahwa:

  • BASH_COMMANDhanya diset ketika sebuah perintah mencoba untuk benar-benar mengevaluasinya; dan
  • itu tidak diset setelah pelaksanaan perintah, meskipun deskripsi dapat membawa kita untuk percaya begitu?

T2: Jika ya, mengapa? Performa? Jika tidak, bagaimana lagi perilaku dalam urutan perintah di atas dapat dijelaskan?

T3: Terakhir, apakah ada skenario di mana variabel ini sebenarnya bisa digunakan secara bermakna? Saya benar-benar mencoba menggunakannya di dalam $PROMPT_COMMANDuntuk menganalisis perintah yang sedang dieksekusi (dan melakukan beberapa hal tergantung pada itu), tetapi saya tidak bisa, karena begitu, dalam saya $PROMPT_COMMAND, saya menjalankan perintah untuk melihat variabel $BASH_COMMAND, variabel mendapat set ke perintah itu. Bahkan ketika saya melakukan yang MYVARIABLE=$BASH_COMMANDbenar di awal saya $PROMPT_COMMAND, kemudian MYVARIABLEberisi string MYVARIABLE=$BASH_COMMAND, karena tugas juga merupakan perintah. (Pertanyaan ini bukan tentang bagaimana saya bisa mendapatkan perintah saat ini dalam $PROMPT_COMMANDeksekusi. Ada cara lain, saya tahu.)

Ini agak mirip dengan prinsip ketidakpastian Heisenberg. Hanya dengan mengamati variabelnya, saya mengubahnya.

Malte Skoruppa
sumber
5
Bagus, tapi mungkin lebih cocok untuk unix.stackexchange.com ... ada bashüber- gurus yang asli di sana.
Rmano
Nah, apakah mungkin untuk memindahkannya ke sana? Mod?
Malte Skoruppa
Saya benar-benar tidak yakin bagaimana cara melakukannya --- saya kira itu adalah hak istimewa Mod. Di sisi lain saya tidak berpikir bahwa mem-flag itu masuk akal --- mari kita lihat apakah seseorang bertindak atas bendera tutup.
Rmano
1
Untuk poin ketiga, inilah yang Anda cari. Mungkin berdasarkan artikel itu Anda dapat menemukan jawaban untuk dua poin pertama juga.
Radu Rădeanu
2
+1 membuat saya bingung, tapi itu menyenangkan untuk dibaca!
Joe

Jawaban:

15

Menjawab pertanyaan ketiga: tentu saja dapat digunakan secara bermakna dengan cara manual Bash secara jelas mengisyaratkan - dalam perangkap, misalnya:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
fgfdjsa failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
cat /etc/fgfdjsa failed with error code 1
Dmitry Alexandrov
sumber
Ah, memang bagus. Jadi, apakah Anda akan mengatakan bahwa perangkap adalah satu-satunya konteks di mana variabel ini masuk akal? Kedengarannya seperti kasus sudut dalam manual: "Perintah dieksekusi (...), kecuali jika shell mengeksekusi perintah sebagai akibat dari jebakan, ..."
Malte Skoruppa
Setelah membaca artikel yang ditautkan dalam jawaban oleh @DocSalvager, jelas bahwa $BASH_COMMANDmemang dapat digunakan secara bermakna bahkan dalam konteks selain jebakan. Namun, penggunaan yang Anda gambarkan mungkin yang paling khas dan mudah. Jadi jawaban Anda, dan yang oleh DocSalvager, jawab Q3 terbaik saya , dan ini yang paling penting bagi saya. Adapun Q1 dan Q2, seperti yang ditunjukkan oleh @zwets, hal-hal itu tidak terdefinisi. Mungkin $BASH_COMMANDhanya diberi nilai jika diakses, untuk kinerja - atau beberapa kondisi program internal yang aneh dapat menyebabkan perilaku itu. Siapa tahu?
Malte Skoruppa
1
Pada sidenote, saya menemukan bahwa Anda dapat memaksa $BASH_COMMANDuntuk selalu ditetapkan, dengan memaksa evaluasi $BASH_COMMANDsebelum setiap perintah. Untuk melakukan ini kita dapat menggunakan perangkap DEBUG, yang dieksekusi sebelum setiap perintah (semua em). Jika kita mengeluarkan, misalnya, trap 'echo "$BASH_COMMAND" > /dev/null' DEBUGmaka $BASH_COMMANDakan selalu dievaluasi sebelum setiap perintah (dan diberi nilai itu $COMMAND). Jadi jika saya mengeksekusi set | grep BASH_COMMAND=saat jebakan itu aktif ... apa yang Anda ketahui? Sekarang hasilnya BASH_COMMAND=set, seperti yang diharapkan :)
Malte Skoruppa
6

Sekarang bahwa Q3 telah dijawab (dengan benar, menurut pendapat saya: BASH_COMMANDberguna dalam perangkap dan hampir tidak di tempat lain), mari kita coba Q1 dan Q2.

Jawaban untuk Q1 adalah: kebenaran asumsi Anda tidak dapat diputuskan. Kebenaran dari poin-poin ini tidak dapat ditentukan, ketika mereka bertanya tentang perilaku yang tidak ditentukan. Dengan spesifikasinya, nilai BASH_COMMANDdiatur ke teks perintah selama durasi eksekusi perintah itu. Spec tidak menyatakan berapa nilainya harus dalam situasi lain, yaitu ketika tidak ada perintah yang dijalankan. Itu bisa memiliki nilai atau tidak sama sekali.

Jawaban untuk Q2 "Jika tidak, bagaimana lagi perilaku dalam urutan perintah di atas dapat dijelaskan?" kemudian mengikuti secara logis (jika agak pedantik): dijelaskan oleh fakta bahwa nilai BASH_COMMANDtidak terdefinisi. Karena nilainya tidak terdefinisi, nilainya dapat memiliki nilai apa pun, yang persis seperti yang ditunjukkan urutannya.

Nota bene

Ada satu titik di mana saya pikir Anda benar-benar memukul titik lemah di spec. Itu tempat Anda mengatakan:

Bahkan ketika saya melakukan MYVARIABLE = $ BASH_COMMAND tepat di awal $ PROMPT_COMMAND saya, maka MYVARIABLE berisi string MYVARIABLE = $ BASH_COMMAND, karena tugas juga merupakan perintah .

Cara saya membaca halaman bash man, bit dalam huruf miring tidak benar. Bagian ini SIMPLE COMMAND EXPANSIONmenjelaskan bagaimana tugas variabel pertama pada baris perintah disisihkan, dan kemudian

Jika tidak ada nama perintah yang dihasilkan [dengan kata lain, hanya ada penugasan variabel], penugasan variabel memengaruhi lingkungan shell saat ini.

Ini menunjukkan kepada saya bahwa tugas variabel bukan perintah (dan karena itu dibebaskan dari muncul di BASH_COMMAND), seperti dalam bahasa pemrograman lain. Ini kemudian juga akan menjelaskan mengapa tidak ada garis BASH_COMMAND=setdalam output set, setpada dasarnya menjadi 'penanda' sintaksis untuk penugasan variabel.

OTOH, dalam paragraf terakhir dari bagian itu dikatakan

Jika ada nama perintah yang tersisa setelah ekspansi, eksekusi dilanjutkan seperti dijelaskan di bawah ini. Jika tidak, perintah akan keluar.

... yang menyarankan sebaliknya, dan penugasan variabel juga merupakan perintah.

zwets
sumber
1
Hanya karena beberapa asumsi tidak dapat ditetapkan bukan berarti itu salah. Einstein mengasumsikan bahwa kecepatan cahaya adalah sama untuk setiap pengamat (dengan kecepatan apa pun) sebagai salah satu hipotesis mendasar untuk teori relativitas; itu tidak dapat dibangun, namun ini tidak berarti itu salah. Properti "dapat dibangun" dan "benar" adalah orthogonal. Paling-paling Anda bisa mengatakan "Kami tidak tahu apakah itu benar, karena itu tidak dapat ditetapkan". Juga, adalah ditentukan: itu perintah saat selama eksekusi. Jadi jika saya mengetik setsaya harus melihat BASH_COMMAND='set'di suatu tempat di output itu, yang
Malte Skoruppa
... namun tidak demikian. Meskipun itu selama eksekusi setperintah . Oleh karena itu, menurut spesifikasi itu harus berfungsi. Jadi apakah implementasinya tidak setia mematuhi spesifikasi, atau apakah saya salah paham spesifikasi? Menemukan jawaban untuk pertanyaan itu adalah semacam tujuan dari Q1. +1 untuk
naskah
@Malte Skoruppa Poin bagus. Perbaiki jawaban yang sesuai, dan tambahkan komentar tentang set.
zwets
1
Mungkin saja dalam bash interpreter, setbukan "perintah". Sama seperti =bukan "perintah". Pemrosesan teks yang mengikuti / mengelilingi token tersebut dapat memiliki logika yang sepenuhnya berbeda dari pemrosesan "perintah."
DocSalvager
Poin bagus dari Anda berdua. :) Mungkin itu settidak dihitung sebagai "perintah". Saya tidak akan mengira itu $BASH_COMMANDakan berubah menjadi terlalu rendah dalam banyak situasi. Saya kira setidaknya kita telah menetapkan bahwa halaman manual pasti bisa lebih jelas tentang hal itu;)
Malte Skoruppa
2

Penggunaan inventif untuk $ BASH_COMMAND

Baru-baru ini menemukan penggunaan $ BASH_COMMAND yang mengesankan dalam mengimplementasikan fungsionalitas seperti makro.

Ini adalah trik inti alias dan menggantikan penggunaan perangkap DEBUG. Jika Anda membaca bagian dalam posting sebelumnya tentang perangkap DEBUG, Anda akan mengenali variabel $ BASH_COMMAND. Dalam posting itu saya mengatakan bahwa itu diatur ke teks perintah sebelum setiap panggilan ke perangkap DEBUG. Ya, ternyata sudah diset sebelum eksekusi setiap perintah, perangkap DEBUG atau tidak (mis. Jalankan 'echo “perintah ini = $ BASH_COMMAND”' untuk melihat apa yang saya bicarakan). Dengan menetapkan variabel (hanya untuk baris itu), kami menangkap BASH_COMMAND pada ruang lingkup terluar dari perintah, yang akan berisi seluruh perintah.

Artikel penulis sebelumnya juga memberikan latar belakang yang baik saat menerapkan teknik menggunakan DEBUG trap. The trapdihilangkan dalam versi perbaikan.

DocSalvager
sumber
+1 Saya akhirnya sempat membaca dua artikel itu. Wow! Apa yang dibaca! Sangat mencerahkan. Orang itu adalah guru bash ! Pujian! Ini juga menjawab komentar saya tentang jawaban Dmitry tentang apakah $BASH_COMMANDdapat digunakan secara bermakna dalam konteks selain a trap. Dan apa gunanya itu! Menggunakannya untuk mengimplementasikan perintah makro di bash - siapa yang mengira itu mungkin? Terima kasih untuk penunjuknya.
Malte Skoruppa
0

Penggunaan yang tampaknya umum untuk jebakan ... debug adalah untuk meningkatkan judul yang muncul di daftar jendela (^ A ") ketika Anda menggunakan" layar ".

Saya mencoba membuat "layar", "daftar jendela" lebih bermanfaat sehingga saya mulai menemukan artikel yang merujuk "perangkap ... debug".

Saya menemukan bahwa metode menggunakan PROMPT_COMMAND untuk mengirim "urutan judul nol" tidak berfungsi dengan baik, jadi saya kembali ke perangkap ... metode debug.

Inilah cara Anda melakukannya:

1 Beri tahu "layar" untuk mencari urutan keluar (aktifkannya) dengan memasukkan "shelltitle '$ | bash:'" ke "$ HOME / .screenrc".

2 Matikan propagasi perangkap debug menjadi sub-shell. Ini penting, karena jika itu diaktifkan, semuanya akan dibuang, gunakan: "set + o functrace".

3 Kirim urutan pelarian judul untuk "layar" untuk ditafsirkan: trap 'printf "\ ek $ (tanggal +% Y% m% d% H% M% S) $ (whoami) @ $ (nama host): $ (pwd) $ {BASH_COMMAND} \ e \ "'" DEBUG "

Itu tidak sempurna, tetapi itu membantu, dan Anda dapat menggunakan metode ini untuk benar-benar memasukkan apa pun yang Anda suka ke dalam judul

Lihat "daftar jendela" berikut (5 layar):

Bendera Nama Num

1 bash: 20161115232035 mcb @ ken007: / home / mcb / ken007 RSYNCCMD = "sudo rsync" myrsync.sh -R -r "$ {1}" "$ {BACKUPDIR} $ {2: +" / $ {2} " } "$ 2 bash: 20161115230434 mcb @ ken007: / home / mcb / ken007 ls --color = auto -la $ bash: 20161115230504 mcb @ ken007: / home / mcb / ken007 cat bin / psg.sh $ 4 bash: 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 $ 5 pesta: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

Malcolm Boekhoff
sumber