Apa yang POSIX butuhkan untuk dokumen yang dikutip di sini di dalam substitusi perintah?

20

Dalam pertanyaan ini seseorang melaporkan masalah menggunakan dokumen di sini dengan kata pembatas yang dikutip di dalam $(...)substitusi perintah , di mana backslash \di akhir baris di dalam dokumen memicu kelanjutan baris baru yang bergabung , sedangkan dokumen yang sama di sini di luar substitusi perintah berfungsi seperti yang diharapkan .

Berikut ini contoh dokumen yang disederhanakan:

cat <<'EOT'
abc ` def
ghi \
jkl
EOT

Ini termasuk satu backtick dan satu backslash di akhir baris. Pembatas dikutip, jadi tidak ada ekspansi yang terjadi di dalam tubuh. Dalam semua Bourne-sama saya dapat menemukan ini menampilkan konten kata demi kata. Jika saya meletakkan dokumen yang sama di dalam substitusi perintah sebagai berikut:

x=$(cat <<'EOT'
abc ` def
ghi \
jkl
EOT
)
echo "$x"

maka mereka tidak lagi berperilaku identik:

  • dash, ash, zsh, ksh93, BusyBox ash, mkshdan SunOS 5.10 POSIX shsemua memberikan isi persis dari dokumen, seperti sebelumnya.
  • Bash 3.2 memberikan kesalahan sintaksis untuk backtick yang tak tertandingi. Dengan backtick yang cocok, ia mencoba menjalankan konten sebagai perintah.
  • Bash 4.3 runtuh "ghi" dan "jkl" ke satu baris, tetapi tidak memiliki kesalahan. The --posixpilihan tidak mempengaruhi ini. Kusalananda memberitahuku (terima kasih!) Yang pdkshberlaku sama .

Dalam pertanyaan awal, saya mengatakan ini adalah bug di parser Bash. Apakah itu? [Perbarui: ya ] Teks yang relevan dari POSIX (semua dari definisi Bahasa Perintah Shell) yang dapat saya temukan adalah:

  • §2.6.3 Substitusi Perintah :

    Dengan formulir $ (perintah), semua karakter yang mengikuti tanda kurung terbuka ke tanda kurung penutup yang cocok merupakan perintah. Setiap skrip shell yang valid dapat digunakan untuk perintah , kecuali skrip yang hanya terdiri dari pengalihan yang menghasilkan hasil yang tidak ditentukan.

  • §2.7.4 Dokumen-Berikut :

    Jika ada bagian kata yang dikutip, pembatas akan dibentuk dengan melakukan penghapusan kutipan pada kata , dan baris dokumen di sini tidak akan diperluas.

  • §2.2.1 Karakter Escape (Backslash) :

    Jika <newline> mengikuti <backslash>, shell harus menafsirkan ini sebagai kelanjutan garis. <backslash> dan <newline> harus dihapus sebelum memisahkan input menjadi token.

  • §2.3 Pengakuan Token :

    Ketika token io_here telah dikenali oleh tata bahasa (lihat Tata Bahasa Shell ), satu atau lebih dari baris berikutnya segera mengikuti token NEWLINE berikutnya membentuk badan dari satu atau lebih dokumen di sini dan harus diuraikan sesuai dengan aturan di sini- dokumen .

    Ketika tidak memproses io_here , shell harus memecah inputnya menjadi token dengan menerapkan aturan pertama yang berlaku di bawah ini ke karakter berikutnya dalam inputnya. ...

    ...

    1. Jika karakter saat ini adalah <backslash>, tanda kutip tunggal, atau tanda kutip ganda dan tidak dikutip, itu akan mempengaruhi mengutip untuk karakter selanjutnya hingga akhir teks yang dikutip. Aturan untuk mengutip adalah sebagaimana dijelaskan dalam Mengutip . Selama pengenalan token, tidak ada pergantian yang benar-benar dilakukan, dan token hasil harus berisi persis karakter yang muncul dalam input (kecuali untuk <newline> bergabung), tidak dimodifikasi, termasuk setiap kutipan atau operator substitusi yang disematkan atau dilampirkan, antara dan akhir dari teks yang dikutip.

Penafsiran saya tentang ini adalah bahwa semua karakter setelah $(hingga terminasi )terdiri dari skrip shell, kata demi kata; dokumen di sini muncul, jadi di sini-pemrosesan dokumen terjadi alih-alih tokenisasi biasa; dokumen di sini kemudian memiliki pembatas yang dikutip, yang artinya bahwa isinya diproses secara kata demi kata; dan karakter pelarian tidak pernah masuk ke dalamnya. Saya dapat melihat argumen, bahwa kasus ini tidak ditangani, dan kedua perilaku tersebut diperbolehkan. Mungkin saja saya juga melewatkan beberapa teks yang relevan di suatu tempat.


  • Apakah situasi ini diperjelas di tempat lain?
  • Apa yang harus diandalkan oleh skrip portabel (secara teori)?
  • Apakah perlakuan khusus diberikan oleh salah satu dari cangkang ini (Bash 3.2 / Bash 4.3 / orang lain) diamanatkan oleh standar? Terlarang? Diizinkan?
Michael Homer
sumber
Bisakah Anda menunjukkan kepada kami bagaimana Anda menghasilkan output Anda dalam kasus kedua?
Julie Pelletier
@JuliePelletier echo "$x", tetapi segala cara memeriksa variabel berfungsi. Saya telah mengedit baris itu ke bawah.
Michael Homer
2
Sepertinya ini adalah perbaikan yang mudah. Tambalan
geirha
1
Saya pikir Anda menafsirkan ini dengan benar dan imo standar cukup jelas karena "Shell akan memperluas substitusi perintah dengan mengeksekusi perintah di lingkungan subshell [...] dan mengganti substitusi perintah [...] dengan output standar dari perintah [...] " Jadi ia menjalankan perintah dalam subkulit dan menggantikan $(...)dengan apa pun keluaran itu ... Sekarang, saat menjalankan perintah dalam contoh Anda dalam subkulit (dalam bash) itu memang menampilkan hasil yang diharapkan. Hanya ketika mengubahnya menjadi substitusi perintah maka itu akan runtuh "ghi" dan "jkl". Jadi ini bug imo
don_crissti
2
@geirha Saya melaporkan bug Bash ; Saya tidak akan peduli tentang pdksh karena tampaknya tidak memiliki bayangan pemeliharaan saat ini.
Michael Homer

Jawaban:

5

Ini ditanyakan pada milis Bash, dan pengelola mengkonfirmasi bahwa itu adalah bug

Mereka juga menyebutkan bahwa teks dalam POSIX "tidak selalu ambigu, tetapi memang membutuhkan pembacaan yang cermat.", Jadi saya meminta klarifikasi tentang itu. Jawaban mereka termasuk deskripsi masalah dan interpretasi standar adalah sebagai berikut:

Substitusi perintah adalah herring merah; itu relevan hanya karena menunjukkan di mana bug itu berada.

Pembatas untuk dokumen di sini dikutip, sehingga garis tidak diperluas. Dalam hal ini, shell membaca baris dari input seolah-olah mereka dikutip. Jika garis miring terbalik muncul dalam konteks di mana ia dikutip, ia tidak bertindak sebagai karakter pelarian (lihat di bawah), dan penanganan khusus garis miring terbalik tidak terjadi. Bahkan, jika ada bagian pembatas yang dikutip, baris dokumen di sini dibaca seolah-olah dikutip tunggal.

Teks dalam Posix 2.2.1 ditulis dengan canggung, tetapi berarti backslash hanya diperlakukan secara khusus ketika tidak dikutip. Anda dapat mengutip garis miring terbalik dan menghambat semua ekspansi hanya dengan tanda kutip tunggal atau garis miring terbalik lainnya.

Bagian bacaan yang dekat adalah teks "tidak diperluas" yang menyiratkan tanda kutip tunggal. Standar mengatakan dalam 2.2 bahwa di sini dokumen adalah "bentuk lain dari kutipan," tetapi satu-satunya bentuk mengutip di mana kata-kata tidak diperluas sama sekali adalah kutipan tunggal. Jadi ini adalah bentuk kutipan yang persis seperti tanda kutip tunggal, tetapi bukan tanda kutip tunggal.

Kevin
sumber
@Scott (1) Saya percaya ini menjawab semua pertanyaan dan tidak ada yang berlebihan. Komentar saya yang dimulai dari jawabannya adalah tentang penghapusan yang dilakukan oleh moderator yang salah paham dengan situasinya. (2) Saya tidak memiliki reputasi yang cukup. (3) Saya akan menghargai perilaku yang sama dengan mereka yang menghapus jawaban saya, tetapi saya pasti akan mengingatnya di masa depan. Terima kasih atas pemikirannya.
Kevin
Maksud saya adalah bahwa sebagian besar paragraf pertama Anda adalah percakapan dengan Michael Mrozek dan bukan jawaban untuk pertanyaan itu. Saya menyadari bahwa Anda tidak memiliki reputasi yang cukup untuk mengomentari pos apa pun, tetapi saya yakin Anda memiliki cukup untuk meta dan obrolan.
Scott
1
@Scott Saya mengerti dan menghargai bahwa Anda mencoba untuk merampingkan jawaban, tetapi saya memposting jawaban yang persis efisien sebelumnya (hanya kutipan dan tautannya), dan itu dihapus oleh moderator tersebut (tanpa diskusi apa pun!) Dan saya tidak melihat tautan di pos yang dihapus untuk mengobrol dan membantah keputusan itu. Saya berharap bahwa dengan menjawab kritiknya yang tidak berdasar, itu akan bertahan dari penghapusan, diterima oleh si penanya dan kemudian saya akan memodifikasi jawaban untuk menghapus pembukaan.
Kevin