Bagaimana cara menggunakan byte nol di Bash?


33

Saya sudah membaca itu, karena path file di Bash dapat berisi karakter apa pun kecuali byte nol (byte bernilai nol, $'\0'), yang terbaik adalah menggunakan byte nol sebagai pemisah. Misalnya, jika output dari findakan dikirim ke program lain, disarankan untuk menggunakan -print0opsi (untuk versi findyang memilikinya).

Tetapi meskipun sesuatu seperti ini berfungsi dengan baik (mencetak jalur file yang dipisahkan oleh baris baru - jangan khawatir, ini hanya demonstrasi, saya tidak benar-benar melakukannya dalam skrip nyata):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

sesuatu seperti ini tidak berfungsi:

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

Ketika saya mencoba hanya bagian for-loop, saya menemukan bahwa itu hanya mencetak semua nama file bersama-sama, tanpa byte nol di antaranya.

Kenapa ini? Apa yang sedang terjadi?

Jawaban:


43

Bash menggunakan string C-style secara internal, yang diakhiri dengan byte nol. Ini berarti bahwa string Bash (seperti nilai variabel, atau argumen ke perintah) tidak pernah bisa benar-benar berisi byte nol. Misalnya, skrip mini ini:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

sebenarnya mencetak 3, karena $foobarsebenarnya hanya 'foo': bardatang setelah akhir string.

Demikian pula, echo $'foo\0bar'cetak saja foo, karena echotidak tahu tentang \0barbagian itu.

Seperti yang Anda lihat, \0urutannya sebenarnya sangat menyesatkan dalam $'...'string-gaya; kelihatannya seperti byte nol di dalam string, tetapi tidak berakhir seperti itu. Dalam contoh pertama Anda, readperintah Anda telah -d $'\0'. Ini berhasil, tetapi hanya karena -d ''juga berfungsi! (Itu bukan fitur yang didokumentasikan secara eksplisit read, tapi saya kira itu bekerja untuk alasan yang sama: ''adalah string kosong, jadi pengakhiran byte nolnya segera datang. Didokumentasikan sebagai menggunakan "Karakter pertama delim ", dan saya kira itu berfungsi jika "karakter pertama" melewati akhir string!)-d delim

Tetapi seperti yang Anda tahu dari findcontoh Anda , adalah mungkin untuk perintah untuk mencetak byte nol, dan untuk byte tersebut akan disalurkan ke perintah lain yang membacanya sebagai input. Tidak ada bagian yang bergantung pada penyimpanan byte nol dalam sebuah string di dalam Bash . Satu-satunya masalah dengan contoh kedua Anda adalah bahwa kami tidak dapat menggunakan $'\0'argumen untuk suatu perintah; echo "$file"$'\0'bisa dengan senang mencetak byte nol di bagian akhir, andai saja ia tahu Anda menginginkannya.

Jadi, alih-alih menggunakan echo, Anda bisa menggunakan printf, yang mendukung jenis pelarian yang sama seperti $'...'string -style. Dengan begitu, Anda dapat mencetak byte nol tanpa harus memiliki byte nol di dalam sebuah string. Itu akan terlihat seperti ini:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

atau hanya ini:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(Catatan: echosebenarnya juga memiliki -eflag yang akan membiarkannya memproses \0dan mencetak byte nol; tetapi kemudian juga akan mencoba untuk memproses urutan khusus apa pun dalam nama file Anda. Jadi printfpendekatannya lebih kuat.)


Kebetulan, ada beberapa kerang yang tidak memungkinkan null byte dalam string. Contoh Anda berfungsi dengan baik di Zsh, misalnya (dengan asumsi pengaturan default). Namun, terlepas dari cangkang Anda, sistem operasi mirip Unix tidak menyediakan cara untuk memasukkan null byte di dalam argumen ke program (karena argumen program dilewatkan sebagai string gaya-C), jadi selalu ada beberapa batasan. (Contoh Anda dapat bekerja di Zsh hanya karena echoshell builtin, jadi Zsh dapat menjalankannya tanpa bergantung pada dukungan OS untuk menjalankan program lain. Jika Anda menggunakannya command echosebagai pengganti echo, sehingga ia mem-by-pass builtin dan menggunakan echoprogram mandiri pada $PATH, Anda akan melihat perilaku yang sama di Zsh seperti di Bash.)


2
Mengapa IFS diatur ke tidak ada jika -d ''sudah berarti membatasi \0? Saya menemukan penjelasan di sini: stackoverflow.com/questions/8677546/…
CMCDragonkai
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.