Saya jelas mengerti bahwa seseorang dapat menambahkan nilai ke variabel pemisah bidang internal. Sebagai contoh:
$ IFS=blah
$ echo "$IFS"
blah
$
Saya juga mengerti bahwa read -r line
akan menyimpan data dari stdin
ke variabel bernama line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Namun, bagaimana suatu perintah dapat memberikan nilai variabel? Dan apakah pertama-tama menyimpan data dari stdin
ke variabel line
dan kemudian memberikan nilai line
ke IFS
?
bash
shell-script
Martin
sumber
sumber
Jawaban:
Beberapa orang memiliki gagasan keliru yang
read
merupakan perintah untuk membaca sebuah baris. Ini bukan.read
membaca kata-kata dari garis (mungkin garis miring terbalik), di mana kata-kata$IFS
dibatasi dan garis miring terbalik dapat digunakan untuk menghindari pembatas (atau melanjutkan garis).Sintaks generik adalah:
read
membaca stdin satu byte pada suatu waktu sampai menemukan karakter newline tidak lolos (atau end-of-input), membagi bahwa menurut aturan yang kompleks dan menyimpan hasil membelah yang ke$word1
,$word2
...$remaining_words
.Misalnya pada input seperti:
dan dengan nilai default
$IFS
,read a b c
akan menetapkan:$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
Sekarang jika hanya melewati satu argumen, itu tidak menjadi
read line
. Itu masihread remaining_words
. Pemrosesan backslash masih dilakukan, karakter spasi IFS masih dihapus dari awal dan akhir.The
-r
pilihan menghilangkan pengolahan backslash. Jadi, perintah yang sama di atas dengan-r
sebaliknya akan menetapkan$a
⇐foo
$b
⇐bar\
$c
⇐baz bl\ah blah\
Sekarang, untuk bagian pemisahan, penting untuk menyadari bahwa ada dua kelas karakter untuk
$IFS
: karakter spasi IFS (yaitu spasi dan tab (dan baris baru, meskipun di sini itu tidak masalah kecuali jika Anda menggunakan -d), yang juga terjadi berada di nilai default$IFS
) dan yang lainnya. Perlakuan untuk dua kelas karakter berbeda.Dengan
IFS=:
(:
menjadi tidak karakter spasi IFS), masukan seperti:foo::bar::
akan dipecah menjadi""
,"foo"
,""
,bar
dan""
(dan tambahan""
dengan beberapa implementasi meskipun itu tidak masalah kecualiread -a
). Sementara jika kita menggantinya:
dengan spasi, pemisahan dilakukan hanya menjadifoo
danbar
. Yang memimpin dan yang tertinggal diabaikan, dan urutannya diperlakukan seperti satu. Ada aturan tambahan saat karakter spasi dan non-spasi putih digabungkan$IFS
. Beberapa implementasi dapat menambah / menghapus perlakuan khusus dengan menggandakan karakter di IFS (IFS=::
atauIFS=' '
).Jadi di sini, jika kita tidak ingin karakter spasi putih terkemuka dan tertinggal dilucuti, kita perlu menghapus karakter spasi putih IFS dari IFS.
Bahkan dengan karakter IFS-non-spasi putih, jika baris input berisi satu (dan hanya satu) karakter tersebut dan itu adalah karakter terakhir dalam baris (seperti
IFS=: read -r word
pada input sepertifoo:
) dengan cangkang POSIX (bukanzsh
atau beberapapdksh
versi), input tersebut dianggap sebagai satufoo
kata karena dalam cangkang itu, karakter$IFS
dianggap sebagai terminator , jadiword
akan berisifoo
, bukanfoo:
.Jadi, cara kanonik untuk membaca satu jalur input dengan
read
builtin adalah:(perhatikan bahwa untuk sebagian besar
read
implementasi, yang hanya berfungsi untuk baris teks karena karakter NUL tidak didukung kecuali dalamzsh
).Menggunakan
var=value cmd
sintaks memastikanIFS
hanya diatur secara berbeda selama durasicmd
perintah itu.Catatan sejarah
The
read
builtin diperkenalkan oleh Bourne shell dan sudah membaca kata-kata , bukan baris. Ada beberapa perbedaan penting dengan cangkang POSIX modern.Shell Bourne
read
tidak mendukung-r
opsi (yang diperkenalkan oleh shell Korn), jadi tidak ada cara untuk menonaktifkan pemrosesan backslash selain pra-pemrosesan input dengan sesuatu seperti dised 's/\\/&&/g'
sana.Shell Bourne tidak memiliki gagasan tentang dua kelas karakter (yang sekali lagi diperkenalkan oleh ksh). Dalam Bourne shell semua karakter menjalani perlakuan yang sama seperti IFS karakter spasi lakukan di ksh, yaitu
IFS=: read a b c
pada input sepertifoo::bar
akan menugaskanbar
untuk$b
, tidak string kosong.Dalam cangkang Bourne, dengan:
Jika
cmd
built-in (sepertiread
ada),var
tetap diatur kevalue
setelahcmd
selesai. Itu sangat penting dengan$IFS
karena dalam shell Bourne,$IFS
digunakan untuk membagi segalanya, tidak hanya ekspansi. Juga, jika Anda menghapus karakter spasi dari$IFS
dalam Bourne shell,"$@"
tidak lagi berfungsi.Di shell Bourne, pengarahan ulang perintah majemuk menyebabkannya berjalan dalam subkulit (dalam versi paling awal, bahkan hal-hal suka
read var < file
atauexec 3< file; read var <&3
tidak berfungsi), jadi jarang di shell Bourne digunakanread
untuk apa pun selain input pengguna pada terminal (di mana penanganan kelanjutan garis itu masuk akal)Beberapa Unices (seperti HP / UX, juga ada satu di dalamnya
util-linux
) masih memilikiline
perintah untuk membaca satu baris input (yang dulunya adalah perintah UNIX standar hingga Spesifikasi Single UNIX versi 2 ).Itu pada dasarnya sama dengan
head -n 1
kecuali bahwa itu membaca satu byte pada suatu waktu untuk memastikan itu tidak membaca lebih dari satu baris. Pada sistem itu, Anda dapat melakukan:Tentu saja, itu berarti memunculkan proses baru, menjalankan perintah dan membaca hasilnya melalui pipa, jadi jauh lebih efisien daripada ksh
IFS= read -r line
, tetapi masih jauh lebih intuitif.sumber
sh
perbedaan reguler juga berguna untuk menulis skrip portabel!)bash-4.4.19
,while read -r; do echo "'$REPLY'"; done
berfungsi sebagaiwhile IFS= read -r line; do echo "'$line'"; done
.read
membaca suatu garis adalah salah, pasti ada sesuatu yang lain. Apa gagasan yang tidak salah itu? Atau apakah pernyataan pertama itu benar secara teknis, tetapi sebenarnya gagasan yang tidak salah adalah: "membaca adalah perintah untuk membaca kata-kata dari sebuah baris. Karena begitu kuat, Anda dapat menggunakannya untuk membaca baris dari file dengan melakukan:IFS= read -r line
"Teori
Ada dua konsep yang berperan di sini:
IFS
adalah Pemisah Bidang Input, yang berarti pembacaan string akan dibagi berdasarkan karakter dalamIFS
. Pada baris perintah,IFS
biasanya karakter spasi apa saja, itu sebabnya baris perintah terbagi spasi.VAR=value command
berarti "memodifikasi lingkungan perintah sehinggaVAR
akan memiliki nilaivalue
". Pada dasarnya, perintahcommand
akan melihatVAR
memiliki nilaivalue
, tetapi setiap perintah yang dieksekusi setelah itu masih akan melihatVAR
memiliki nilai sebelumnya. Dengan kata lain, variabel itu hanya akan dimodifikasi untuk pernyataan itu.Pada kasus ini
Jadi ketika melakukan
IFS= read -r line
, apa yang Anda lakukan adalah mengaturIFS
ke string kosong (tidak ada karakter yang akan digunakan untuk membelah, oleh karena itu tidak akan terjadi pemisahan) sehinggaread
akan membaca seluruh baris dan melihatnya sebagai satu kata yang akan ditugaskan keline
variabel. PerubahanIFS
hanya mempengaruhi pernyataan itu, sehingga perintah berikut tidak akan terpengaruh oleh perubahan.Sebagai catatan
Sementara perintah adalah benar dan akan bekerja sebagaimana dimaksud, pengaturan
IFS
dalam hal initidaksekuat 1 tidak diperlukan. Seperti yang tertulis dibash
halaman manual di bagianread
builtin:Karena Anda hanya memiliki
line
variabel, toh setiap kata akan ditugaskan untuk itu, jadi jika Anda tidak memerlukan karakter spasi putih sebelumnya dan trailing 1, Anda bisa menulisread -r line
dan selesai melakukannya.[1] Sama seperti contoh bagaimana suatu nilai
unset
default$IFS
akan menyebabkanread
menganggap / membuntuti spasi IFS , Anda dapat mencoba:Jalankan dan Anda akan melihat bahwa karakter sebelumnya dan trailing tidak akan bertahan jika
IFS
tidak disetel. Selain itu, beberapa hal aneh bisa terjadi jika$IFS
harus dimodifikasi di suatu tempat sebelumnya dalam skrip.sumber
Anda harus membaca pernyataan itu dalam dua bagian, yang pertama membersihkan nilai variabel IFS, yaitu setara dengan yang lebih mudah dibaca
IFS=""
, yang kedua membacaline
variabel dari stdinread -r line
,.Apa yang spesifik dalam sintaks ini adalah pengaruh IFS yang transcient dan hanya valid untuk
read
perintah.Kecuali jika saya melewatkan sesuatu, dalam hal itu kliringIFS
tidak memiliki efek apa pun karena apa punIFS
yang diatur, seluruh baris akan dibaca dalamline
variabel. Akan ada perubahan perilaku hanya dalam kasus ini lebih dari satu variabel telah dilewati sebagai parameter untukread
instruksi.Sunting:
Itu
-r
ada untuk memungkinkan input berakhir dengan\
tidak akan diproses secara khusus, yaitu untuk backslash untuk dimasukkan dalamline
variabel dan bukan sebagai karakter lanjutan untuk memungkinkan input multi-line.Menghapus IFS memiliki efek samping mencegah pembacaan untuk memangkas karakter tab atau spasi potensial dan tertinggal, misalnya:
Terima kasih kepada Rici untuk menunjukkan perbedaan itu.
sumber
read -r line
akan memangkas spasi awal dan akhir sebelum menetapkan input keline
variabel.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"
akan ditampilkan-aa bb--