Menetapkan opsi bash dalam perintah majemuk

9

Saya telah menemukan bahwa pengaturan extglobopsi shell di dalam senyawa majemuk menyebabkan kegagalan anti-gumpalan berikutnya. Apakah opsi shell harus diatur di luar perintah majemuk? Saya tidak melihat indikasi persyaratan seperti itu di halaman bash man.

Sebagai contoh, skrip berikut berfungsi dengan baik (cetak a.0 a.1):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
shopt -s extglob
ls "a."!(b*)

Namun, jika dua baris terakhir dieksekusi sebagai perintah majemuk, skrip gagal dengan kesalahan berikut:

syntax error near unexpected token `('
`    ls "a."!(b*)'

Ini diuji menggunakan versi bash dari 4.2 hingga 4.4 dan dengan berbagai perintah majemuk :

(1) bersyarat - if

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
if true; then
    shopt -s extglob
    ls "a."!(b*)
fi

(2) kawat gigi - { }

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
{
    shopt -s extglob
    ls "a."!(b*)
}

(3) subkulit - ( ):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
(
    shopt -s extglob
    ls "a."!(b*)
)

Dalam semua kasus, jika shoptdipindahkan di luar perintah majemuk, skrip berhasil.

user001
sumber

Jawaban:

14

Tidak ada bagian dari perintah gabungan dieksekusi sebelum seluruh perintah diuraikan, yang secara tidak wajar didokumentasikan di bagian Operasi Shell manual: semua tokenisasi dan penguraian terjadi sebelum "" perintah dieksekusi. Mengaktifkan extglobmengubah sintaksis bahasa dengan menambahkan operator yang cocok dengan pola, yang dikenali selama tokenisasi.

Karena shoptperintah belum dieksekusi pada saat !(tercapai, itu dilihat sebagai percobaan ekspansi sejarah ( !) atau operator kontrol (, daripada !(dilihat sebagai satu item (dan sama untuk @(, dll, kecuali bahwa tidak ada ekspansi sejarah sana).

Ketika parsing mencapai token itu, kedua case umumnya akan menjadi kesalahan, meskipun !(...)pada awal garis atau setelahnya timeadalah pipa subkulit yang dinegasikan. " unexpected token `('" artinya itu tidak dapat menerima ekspresi subkulit di tempat itu.

Ini agak menjengkelkan ketika Anda ingin ekstglob diaktifkan hanya sementara di subkulit. Salah satu solusinya adalah mendefinisikan fungsi yang menggunakan glob yang Anda inginkan, kemudian nonaktifkan extglob, dan kemudian panggil fungsi dari subshell dengan extglob diaktifkan:

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
    shopt -s extglob
    f
)

Ini sangat kikuk.


Efek yang sama terjadi jika Anda membuat alias dalam perintah majemuk, karena mereka juga diproses sebelum penguraian:

if true
then
    alias foo=echo
    foo bar
fi
foo xyz

akan mencetak foo: command not found, dan kemudian xyz, karena alias yang dibuat, tetapi tidak tersedia sampai ifselesai.

Michael Homer
sumber
Terima kasih - saya tidak menyadari bahwa perintah majemuk diurai sebagai unit sebelum eksekusi. Perilaku itu sekarang masuk akal.
user001
3
@ user001 Saya tidak akan begitu murah hati untuk mengatakan bahwa itu masuk akal. Mengganti mode lexer saat parsing tidak pernah terdengar sebelumnya, dan bahasa lain suka Catau perlbisa melakukannya dengan baik. Perhatikan bahwa tidak menjadi bagian dari perintah majemuk tidak cukup, shopt -s extglobjuga harus berada di jalur terpisah. Lihat juga diskusi dan contoh di sini
mosvy
@ Mosvy: Maksud saya perilaku yang diamati masuk akal begitu seseorang menyadari bahwa bash mem-parsing perintah majemuk sebagai satu unit (bukan bahwa batasan parser itu wajar-wajar saja). Terima kasih telah menautkan ke diskusi di pos lain.
user001