Dalam `while IFS = read..`, mengapa IFS tidak berpengaruh?

12

Saya mungkin memiliki sesuatu yang benar-benar salah, tetapi terlihat meyakinkan bagi saya, bahwa menetapkan IFS sebagai salah satu perintah dalam daftar pra-lakukan / selesai sama sekali tidak berpengaruh.
IFS luar (di luar whilekonstruk) berlaku dalam semua contoh yang ditunjukkan dalam skrip di bawah ini ..

Apa yang terjadi di sini? Apakah saya mendapat ide yang salah tentang apa yang dilakukan IFS dalam situasi ini? Saya mengharapkan hasil array-split seperti yang ditunjukkan pada kolom "diharapkan".


#!/bin/bash
xifs() { echo -n "$(echo -n "$IFS" | xxd -p)"; } # allow for null $IFS 
show() { x=($1) 
         echo -ne "  (${#x[@]})\t |"
         for ((j=0;j<${#x[@]};j++)); do 
           echo -n "${x[j]}|"
         done
         echo -ne "\t"
         xifs "$IFS"; echo
}
data="a  b   c"
echo -e "-----   --  -- \t --------\tactual"
echo -e "outside        \t  IFS    \tinside" 
echo -e "loop           \t Field   \tloop" 
echo -e "IFS     NR  NF \t Split   \tIFS (actual)" 
echo -e "-----   --  -- \t --------\t-----"
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 1'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 2'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 3'; show "$REPLY"; done
IFS=" ";      xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 4'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 5'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 6'; show "$REPLY"; done
IFS=;         xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 7'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t 8'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 9'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t10'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t11'; show "$REPLY"; done
echo -e "-----   --  -- \t --------\t-----"

Keluaran:

-----   --  --   --------       actual   
outside           IFS           inside                assigned   
loop             Field          loop    #              inner
IFS     NR  NF   Split          IFS     #  expected    IFS
-----   --  --   --------       -----   #  ---------  --------
20090a   1  (3)  |a|b|c|        20090a  #                              
20090a   2  (3)  |a|b|c|        20090a  #  |a  b   c|  IFS=
20090a   3  (3)  |a|b|c|        20090a  #  |a  |   c|  IFS=b
20       4  (3)  |a|b|c|        20      #                          
20       5  (3)  |a|b|c|        20      #  |a  b   c   IFS=
20       6  (3)  |a|b|c|        20      #  |a  |   c|  IFS=b
         7  (1)  |a  b   c|             #                          
         8  (1)  |a  b   c|             #  |a|b|c|     IFS=" "
         9  (1)  |a  b   c|             #  |a  |   c|  IFS=b
62      10  (2)  |a  |   c|     62      #  |a  b   c|  IFS=
62      11  (2)  |a  |   c|     62      #  |a|b|c|     IFS=" "
-----   --  --   --------       -----      ---------   -------                        
Peter.O
sumber

Jawaban:

17

(Maaf, penjelasan panjang)

Ya, IFSvariabel dalam while IFS=" " read; do …tidak berpengaruh pada sisa kode.

Pertama mari kita tepatkan bahwa baris perintah shell menampilkan dua jenis variabel:

  • variabel shell (yang hanya ada di dalam shell, dan bersifat lokal ke shell)
  • variabel lingkungan, yang ada untuk setiap proses. Itu biasanya dilestarikan fork()dan exec(), jadi proses anak mewarisinya.

Saat Anda memanggil perintah dengan:

  A=foo B=bar command

perintah dieksekusi di dalam lingkungan di mana variabel (lingkungan) Adiatur ke foodan Bdiatur ke bar. Tetapi dengan baris perintah ini, variabel shell saat ini Adan Bdibiarkan tidak berubah .

Ini berbeda dari:

A=foo; B=bar; command

Di sini, variabel shell Adan Bdidefinisikan dan perintah dijalankan tanpa variabel lingkungan Adan Bdidefinisikan. Nilai dari Adan Btidak dapat diakses dari command.

Namun, jika beberapa variabel shell export-ed, variabel lingkungan yang sesuai disinkronkan dengan variabel shell masing-masing. Contoh:

export A
export B
A=foo; B=bar; command

Dengan kode ini, variabel shell dan variabel lingkungan shell diatur ke foodan bar. Karena variabel lingkungan diwarisi oleh sub-proses, commandakan dapat mengakses nilainya.

Untuk kembali ke pertanyaan awal Anda, di:

IFS='a' read

hanya readterpengaruh. Dan pada kenyataannya, dalam hal ini, readtidak peduli dengan nilai IFSvariabel. Ini IFShanya digunakan ketika Anda meminta baris untuk dibagi (dan disimpan dalam beberapa variabel), seperti di:

echo "a :  b :    c" | IFS=":" read i j k; \
    printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"

IFStidak digunakan oleh readkecuali disebut dengan argumen. ( Sunting: Ini tidak sepenuhnya benar: karakter spasi putih, yaitu spasi dan tab, yang ada IFSselalu diabaikan di awal / akhir baris input.)

Stéphane Gimenez
sumber
Sungguh penjelasan yang bagus! Sederhana sekali! Saya bingung dengan sintaks 'tanpa semi-kolon' selama berbulan-bulan; dan itu hanya kasus itu berarti variabel lokal! .. rozcietrzewiacz membuka jalur untuk saya (waktu besar) dalam pertanyaan lain ... dan Anda baru saja menaruh lapisan gula pada kue ... Aku sudah bangun sepanjang malam untuk yang satu ini, dan sudah pasti layak untuk jawaban yang begitu baik dan jelas! .. Terima kasih ..
Peter.O
Uhm. Saya harus membaca komentar edit itu beberapa kali sebelum mendapatkannya - maksud Anda mengatakan bahwa karakter spasi putih yang ada $IFSdihilangkan pada awal / akhir baris input, saya kira? (Begitulah cara kerjanya.)
zrajm
Nilai IFS adalah penting bahkan ketika membaca variabel tunggal, karena shell masih melakukan kata membelah pada input. Jadi misalnya, mengetikkan karakter a<tab>bke dalam read varakan menghasilkan var memiliki nilai a<space>b, tetapi jika sebaliknya Anda miliki IFS='<newline>' read varmaka nilai var akan menjadi a<tab>b.
John Hascall
8

Sederhananya, Anda harus membaca lebih dari satu variabel pada suatu waktu agar IFS=<something> read ...konstruk memiliki efek yang terlihat dalam contoh Anda 1 .

Anda melewatkan ruang lingkup readdalam contoh. Tidak ada modifikasi IFS di dalam loop dalam kasus uji Anda. Izinkan saya menunjukkan dengan tepat, di mana IFS kedua memiliki efek di setiap baris Anda:

 IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo ...
                                                      ^      ^
                                                      |      |
                                          from here --'       `- to here :)

Sama seperti program apa pun yang dijalankan di shell. Variabel yang Anda (re) tentukan di baris perintah memengaruhi eksekusi program. Dan hanya itu (karena Anda tidak mengekspor). Oleh karena itu, untuk menggunakan definisi ulang IFSdi baris tersebut, Anda harus meminta readuntuk menetapkan nilai ke lebih dari satu variabel . Lihatlah contoh-contoh ini:

 $ data="a  b   c"
 $ echo "$data" | while           read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a|b||c|
 $ echo "$data" | while IFS=      read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a b c||||
 $ echo "$data" | while IFS='a'   read A B C; do echo \|$A\|$B\|\|$C\|; done
 || b c|||
 $ echo "$data" | while IFS='ab'  read A B C; do echo \|$A\|$B\|\|$C\|; done
 || || c|

1 Seperti yang baru saja saya pelajari dari Gilles , sebenarnya mungkin ada manfaat dari pengaturan IFS=''(kosong) ketika membaca hanya satu bidang: ia menghindari pemotongan spasi putih di awal baris.

rozcietrzewiacz
sumber
Bagus .. Terima kasih ... Saya mendapatkannya kali ini .. dan saya suka sketsa Anda :)
Peter.O
OK, sekarang saya sudah membaca komentar Anda yang melihat Anda belum memperhatikan jawaban saya untuk masalah itu di pertanyaan lain. Mungkin Anda bisa mengembalikan yang lain dan menghapus ini, karena ini benar-benar masalah umum?
rozcietrzewiacz
Ya, kedua pertanyaan memiliki tema yang terkait, tetapi judul yang lain adalah "Mengapa IFS= readdigunakan sebagai preferensi untuk hanya mengatur ulang variabel lingkungan IFS". Saya tidak memiliki kesadaran, kemudian, bahwa variabel lokal dapat ditetapkan oleh pemanggil perintah. Itulah jawaban untuk pertanyaan itu. Itu memang berevolusi lebih jauh untuk mengatasi poin utama pertanyaan ini, tetapi pada saat saya menyadari bahwa, saya sudah mengajukan pertanyaan ini ... Mungkin dua pertanyaan itu sama seperti dua sedpertanyaan, jadi perasaan saya untuk menyimpannya sebagai ... Lebih banyak judul untuk googler ke google.
Peter.O