it-swarm-id.com

Mengapa `sementara IFS = read` sering digunakan, alih-alih` IFS =; saat membaca..`?

Tampaknya praktik normal akan menempatkan pengaturan IFS di luar loop sementara agar tidak mengulangi pengaturan untuk setiap iterasi ... Apakah ini hanya gaya "monyet lihat, monyet lakukan", seperti yang telah dilakukan untuk monyet ini sampai Saya membaca baca manual, atau apakah saya melewatkan beberapa perangkap yang halus (atau sangat jelas) di sini?

85
Peter.O

Perangkapnya adalah itu

IFS=; while read..

mengatur IFS untuk seluruh lingkungan Shell di luar loop, sedangkan

while IFS= read

mendefinisikan ulang hanya untuk permohonan read (kecuali di Shell Bourne). Anda dapat memeriksa apakah melakukan loop seperti

while IFS= read xxx; ... done

lalu setelah loop seperti itu, echo "blabalbla $IFS ooooooo" mencetak

blabalbla
 ooooooo

sedangkan sesudahnya

IFS=; read xxx; ... done

IFS tetap didefinisikan ulang: sekarang echo "blabalbla $IFS ooooooo" mencetak

blabalbla  ooooooo

Jadi jika Anda menggunakan formulir kedua, Anda harus ingat untuk mengatur ulang: IFS=$' \t\n'.


Bagian kedua dari pertanyaan ini telah digabung di sini , jadi saya telah menghapus jawaban terkait dari sini.

86
rozcietrzewiacz

Mari kita lihat contoh, dengan beberapa input teks yang dibuat dengan cermat:

text=' hello  world\
foo\bar'

Itu dua baris, yang pertama dimulai dengan spasi dan diakhiri dengan garis miring terbalik. Pertama, mari kita lihat apa yang terjadi tanpa tindakan pencegahan sekitar read (tetapi menggunakan printf '%s\n' "$text" untuk mencetak dengan hati-hati $text tanpa risiko ekspansi). (Di bawah, $ ‌ adalah Prompt Shell.)

$ printf '%s\n' "$text" |
  while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]

read memakan backslash: backslash-newline menyebabkan baris baru diabaikan, dan backslash-apa pun mengabaikan backslash pertama. Untuk menghindari backsash yang diperlakukan secara khusus, kami menggunakan read -r.

$ printf '%s\n' "$text" |
  while read -r line; do printf '%s\n' "[$line]"; done
[hello  world\]
[foo\bar]

Itu lebih baik, kami memiliki dua garis seperti yang diharapkan. Dua baris tersebut hampir berisi konten yang diinginkan: spasi ganda antara hello dan world telah dipertahankan, karena itu masih dalam variabel line. Di sisi lain, ruang awal dimakan. Itu karena read membaca banyak kata saat Anda meneruskan variabel, kecuali bahwa variabel terakhir berisi sisa baris - tetapi masih dimulai dengan Word pertama, mis. Spasi awal dibuang.

Jadi, untuk membaca setiap baris secara harfiah, kita perlu memastikan bahwa tidak ada pemisahan kata sedang terjadi. Kami melakukan ini dengan menyetel variabel IFS ke nilai kosong.

$ printf '%s\n' "$text" |
  while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello  world\]
[foo\bar]

Perhatikan bagaimana kita mengatur IFS khusus untuk durasi read built-in . IFS= read -r line mengatur variabel lingkungan IFS (ke nilai kosong) khusus untuk eksekusi read. Ini adalah contoh umum perintah sederhana sintaks: urutan (mungkin kosong) dari penugasan variabel diikuti oleh nama perintah dan argumennya (juga, Anda dapat melempar pengalihan pada titik mana pun). Karena read adalah bawaan, variabel tidak pernah benar-benar berakhir di lingkungan proses eksternal; meskipun demikian nilai $IFS adalah apa yang kami tetapkan di sana selama read sedang dijalankan¹. Perhatikan bahwa read bukan built-in khusus , jadi tugasnya hanya berlangsung selama durasinya.

Karenanya kami berhati-hati untuk tidak mengubah nilai IFS untuk instruksi lain yang mungkin bergantung padanya. Kode ini akan berfungsi tidak peduli apa kode di sekitarnya telah menetapkan IFS untuk awalnya, dan itu tidak akan menimbulkan masalah jika kode di dalam loop bergantung pada IFS.

Kontras dengan cuplikan kode ini, yang mencari file di jalur yang dipisahkan titik dua. Daftar nama file dibaca dari file, satu nama file per baris.

IFS=":"; set -f
while IFS= read -r name; do
  for dir in $PATH; do
    ## At this point, "$IFS" is still ":"
    if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
  done
done <filenames.txt

Jika loop itu while IFS=; read -r name; do …, kemudian for dir in $PATH tidak akan terpecah $PATH menjadi komponen yang dipisahkan oleh titik dua. Jika kodenya IFS=; while read …, akan semakin jelas bahwa IFS tidak disetel ke : dalam tubuh loop.

Tentu saja, adalah mungkin untuk mengembalikan nilai IFS setelah menjalankan read. Tetapi itu membutuhkan pengetahuan tentang nilai sebelumnya, yang merupakan upaya ekstra. IFS= read adalah cara sederhana (dan, juga, cara terpendek).

¹ Dan, jika read terganggu oleh sinyal yang terperangkap, mungkin saat perangkap dieksekusi - ini tidak ditentukan oleh POSIX dan tergantung pada Shell dalam praktiknya.

Selain dari (sudah diklarifikasi) IFS perbedaan cakupan antara while IFS='' read, IFS=''; while read dan while IFS=''; read idiom (per-perintah vs skrip/Shell-wide IFS variabel scoping), pelajaran yang bisa dibawa pulang adalah bahwa Anda kehilangan pimpinan dan tertinggal spasi dari jalur input jika variabel IFS diatur ke (berisi a) spasi.

Ini dapat memiliki konsekuensi yang cukup serius jika jalur file sedang diproses.

Oleh karena itu pengaturan variabel IFS ke string kosong sama sekali bukan ide yang buruk karena hal itu memastikan bahwa spasi spasi awal dan akhir garis tidak dilucuti.

Lihat juga: Bash, baca baris demi baris dari file, dengan IFS

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)
3
jon

Terinspirasi oleh Jawaban Yuzem

Jika Anda ingin mengatur IFS ke karakter yang sebenarnya, ini berhasil bagi saya

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
1
Steven Penny