Apa argumen terakhir dari perintah sebelumnya?

12

$_ dikatakan sebagai argumen terakhir dari perintah sebelumnya.

Jadi saya bertanya-tanya mengapa itu tidak ada EDITOR="emacs -nw"tetapi EDITORdalam contoh berikut?

Mengapa bukan "emacs -nw"bagian dari argumen terakhir?

Secara umum, apa definisi argumen, dan argumen terakhir?

Terima kasih.

$ export EDITOR="emacs -nw"
$ echo $_
EDITOR
Tim
sumber
3
Saya pikir itu karena alasan yang sama bahwa shellcheck memberitahu Anda untuk tidak mengekspor variabel pada baris yang sama dengan yang Anda tetapkan. Penugasan terjadi dan kemudian variabel diekspor. EDITORadalah argumen untuk mengekspor
jesse_b
FWIW, pdkshdan dashakan mencakup nilai yang ditugaskan, tetapi ksh93akan berperilaku seperti bashhalnya.
Kusalananda
zsh:, export FOO=bar; echo $_mencetak export.
ilkkachu
@Jesse_b Semuanya adalah argumen / operan terakhir (termasuk nilai yang diberikan), tetapi mungkin ada hubungannya dengan fakta bahwa itu exportadalah utilitas bawaan.
Kusalananda
ksh: typeset -x FOO=barlalu echo $_dicetak FOO, tetapi dalam declare -x FOO=bar; echo $_cetak Bash FOO=bar.
ilkkachu

Jawaban:

13

Bash memproses tugas variabel, ketika mereka diizinkan sebagai argumen (dengan alias, declare, export, local, readonly, dan typeset), sebelum hal lain (atau lebih tepatnya, itu mengidentifikasi mereka sebelum hal lain - ekspansi berlaku untuk nilai-nilai ditugaskan untuk variabel). Ketika sampai ke ekspansi kata, perintah yang tersisa adalah export EDITOR, jadi _diatur ke EDITOR.

Secara umum, argumen adalah "kata-kata" yang tersisa setelah ekspansi (yang tidak termasuk tugas variabel dan pengalihan).

Lihat Ekspansi perintah sederhana di manual Bash untuk detailnya.

Stephen Kitt
sumber
Dan saya menyadari declareperilaku tidak sesuai dengan apa yang saya gambarkan ...
Stephen Kitt
Yah, itu tidak terlalu konsisten dalam hal itu. declare a=b; echo $_cetakan a=b; export c=d; echo $_hanya mencetak c. aliastampaknya hanya mencetak nama, localdi sisi lain mencetak seluruh argumen. Dan readonlyjuga mencetak hanya nama, yang saya temukan sedikit mengejutkan karena saya akan berpikir readonlydan localakan mirip declare.
ilkkachu
1
@ilkkachu heh, saya juga menyadarinya (lihat di atas). exportdan readonlydinyatakan bersama-sama dalam setattr.def, declare, local, dan typesetdinyatakan dalam declare.def, aliasberdiri sendiri di alias.def.
Stephen Kitt
Terima kasih. Ketika tugas variabel digunakan sebagai argumen untuk beberapa perintah, (1) "(atau lebih tepatnya, itu mengidentifikasi mereka sebelum hal lain - ekspansi berlaku untuk nilai-nilai yang ditugaskan untuk variabel)", apakah maksud Anda ekspansi terjadi pada nilai-nilai sebelum melakukan tugas variabel? (2) "Ketika sampai pada kata ekspansi, perintah yang tersisa adalah ekspor EDITOR", maksud Anda melakukan penugasan variabel terjadi sebelum ekspansi? Kedua kutipan itu tampaknya saling bertentangan.
Tim
Terima kasih. Saya sedikit bingung. Ketika tugas variabel digunakan sebagai argumen untuk alias, declare, export, local, readonlydan typeset. Apa yang terjadi pertama dan selanjutnya? "Ketika sampai pada kata ekspansi, perintah yang tersisa adalah export EDITOR", apakah Anda menyiratkan bahwa penugasan variabel EDITOR="emacs -nw"terjadi sebelum ekspansi? Jika tidak, mengapa perintah yang tersisa tidak berisi tugas sebagai argumen? Jika ya, bukankah ekspansi pada nilai yang ditugaskan ke variabel harus terjadi sebelum melakukan penugasan variabel?
Tim
4

TL; DR: Dalam kasus export FOO=bar, bash memanggil penciptaan lingkungan sementara, menetapkan FOO=bardalam lingkungan itu, lalu menghasilkan perintah terakhir dari export FOO. Pada titik itu, FOOdiambil sebagai argumen terakhir.


Ah, yang paling banyak disalahgunakan $_:

($ _, sebuah garis bawah.) Pada startup shell, setel ke pathname absolut yang digunakan untuk memanggil shell atau skrip shell yang dieksekusi sebagaimana diteruskan dalam daftar lingkungan atau argumen. Selanjutnya, perluas argumen terakhir ke perintah sebelumnya, setelah ekspansi. Juga setel ke nama path lengkap yang digunakan untuk memanggil setiap perintah yang dieksekusi dan ditempatkan di lingkungan yang diekspor ke perintah itu. Saat memeriksa email, parameter ini menyimpan nama file email.

Mari kita lihat beberapa variasi:

$ man; echo $_
What manual page do you want?
man
$ man foo; echo $_
No manual entry for foo
foo
$ echo; echo $_

echo
$ echo bar foo; echo $_
bar foo
foo
$ foo=x eval 'echo $foo'; echo $_
x
echo $foo
$ bar() { man $1; }; echo $_
foo
$ for (( i=0; $i<0; i=i+1 )); do echo $i; done; echo $_
foo
$ bar; echo $_
What manual page do you want?
man
$ bar foo; echo $_
No manual entry for foo
foo
$ MANPATH=/tmp; echo $_

$ export MANPATH=/tmp; echo $_
MANPATH

Jadi kita melihat tiga pola di sini:

  • Perintah yang dipanggil dari filesystem, fungsi, dan built-in berperilaku seperti yang diharapkan secara umum: $_diatur ke nama perintah itu sendiri jika tidak ada argumen, kalau tidak argumen terakhir yang disajikan.
  • Setelah definisi fungsi, loop, dan konstruksi logis lainnya: $_tidak diubah.
  • Yang lainnya: $_diatur ke sesuatu yang tidak terlalu diharapkan; aneh.

Saya telah memasukkan kode untuk memberikan wawasan tentang keanehan.

$ ./bash --noprofile --norc -c 'man foo'
lastword=[man]
lastarg=[foo]
$ ./bash --noprofile --norc -c 'export FOO=bar'
lastword=[export]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[bar]
before bind_lastarg, lastarg=[FOO]
bind_lastarg, arg=[FOO]
bind_variable, name=[_], value=[FOO]
$ ./bash --noprofile --norc -c 'declare FOO=bar'
lastword=[declare]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[(null)]
before bind_lastarg, lastarg=[FOO=bar]
bind_lastarg, arg=[FOO=bar]
bind_variable, name=[_], value=[FOO=bar]

Anda dapat melihat bahwa parser melihat argumen terakhir yang diharapkan ( lastarg=) dalam semua kasus, tetapi apa yang terjadi setelahnya tergantung pada apa yang menurut bash harus terjadi. Lihat execute_cmd.c, execute_simple_command () .

Dalam kasus export FOO=bar, bash membuat tugas dan kemudian mengekspor variabel. Ini tampaknya konsisten dengan pernyataan dokumentasi bahwa argumen terakhir dihitung setelah ekspansi.

uskup
sumber
1
Bagaimana shell tahu Anda memeriksa email?
rackandboneman
@rackandboneman tanpa konfirmasi, saya curiga pemeriksaan internal dilakukan berdasarkanMAILCHECK
Jeff Schaller
2

Untuk menjawab pertanyaan judul, cobalah !$:

$ export EDITOR="emacs -nw"
$ echo !$
EDITOR=emacs -nw

Ini adalah ekspansi sejarah. Dari halaman bash:

Ekspansi sejarah dilakukan segera setelah baris lengkap dibaca, sebelum shell memecahnya menjadi kata-kata. Itu terjadi dalam dua bagian. Yang pertama adalah menentukan baris mana dari daftar riwayat yang akan digunakan selama substitusi. Yang kedua adalah memilih bagian-bagian dari garis itu untuk dimasukkan ke dalam yang sekarang. Garis yang dipilih dari sejarah adalah peristiwa, dan bagian-bagian dari garis yang ditindaklanjuti adalah kata-kata.

...

Desainer Acara

...

! Mulai substitusi riwayat, kecuali bila diikuti oleh baris kosong, baris baru, carriage return, = atau ((ketika opsi extglob shell diaktifkan menggunakan shopt builtin).

...

!! Lihat perintah sebelumnya. Ini adalah sinonim untuk `! -1 '.

...

Penentu Kata

...

$ Kata terakhir. Ini biasanya argumen terakhir, tetapi akan diperluas ke kata nol jika hanya ada satu kata di baris.

...

Jika kata designator diberikan tanpa spesifikasi acara, perintah sebelumnya digunakan sebagai acara.

JoL
sumber
Anda mengambil judul pertanyaan waaaaay terlalu harfiah. (OK, itu judul yang buruk.) Kita semua dapat melihat bahwa perintah « export EDITOR="emacs -nw"» terdiri dari dua kata: yang pertama adalah « export» dan yang kedua adalah « EDITOR="emacs -nw"». Pertanyaannya adalah benar-benar bertanya, "Apa yang dimaksud dengan halaman bash man dan Manual Bash ketika mereka mengatakan bahwa !_'memperluas argumen terakhir ke perintah sebelumnya', mengingat bahwa bash menetapkan $_« EDITORdalam kasus ini? " Menyalin dan menempelkan bagian halaman bash man tentang ekspansi sejarah tidak terlalu membantu.
G-Man Mengatakan 'Reinstate Monica'