Saya jelas mengerti bahwa seseorang dapat menambahkan nilai ke variabel pemisah bidang internal. Misalnya:
$ 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 sebuah perintah dapat menetapkan nilai variabel? Dan apakah itu pertama kali menyimpan data dari stdin
ke variabel line
lalu beri nilai line
ke IFS
?
Jawaban yang Diterima:
Dalam shell POSIX, read
, tanpa opsi apa pun tidak membaca baris , itu membaca kata dari baris (mungkin backslash-continued), dengan kata-kata $IFS
delimited dan backslash dapat digunakan untuk menghindari pembatas (atau melanjutkan baris).
Sintaks umum adalah:
read word1 word2... remaining_words
read
membaca stdin satu byte pada satu waktu¹ sampai menemukan karakter baris baru yang tidak terhapus (atau akhir input), membaginya menurut aturan yang kompleks dan menyimpan hasil pemisahan itu menjadi $word1
, $word2
… $remaining_words
.
Misalnya pada input seperti:
<tab> foo bar baz blah blah
whatever whatever
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
. Masih read remaining_words
. Pemrosesan garis miring terbalik masih dilakukan, karakter spasi putih IFS² masih dihapus dari awal dan akhir.
-r
opsi menghapus pemrosesan garis miring terbalik. Jadi perintah yang sama di atas dengan -r
sebagai gantinya akan menetapkan
$a
foo
$b
bar
$c
baz blah blah
Sekarang, untuk bagian pemisahan, penting untuk disadari bahwa ada dua kelas karakter untuk $IFS
:karakter spasi putih IFS² (termasuk spasi dan tab (dan baris baru, meskipun di sini tidak masalah kecuali Anda menggunakan -d), yang juga merupakan nilai default $IFS
) dan yang lainnya. Perlakuan untuk kedua kelas karakter tersebut berbeda.
Dengan IFS=:
(:
bukan karakter spasi putih IFS), masukan seperti :foo::bar::
akan dipecah menjadi ""
, "foo"
, ""
, bar
dan ""
(dan tambahan ""
dengan beberapa implementasi meskipun itu tidak masalah kecuali untuk read -a
). Sedangkan jika kita mengganti :
dengan spasi, pemisahan dilakukan menjadi hanya foo
dan bar
. Yang memimpin dan yang tertinggal diabaikan, dan urutannya diperlakukan seperti satu. Ada aturan tambahan ketika karakter spasi dan non-spasi digabungkan dalam $IFS
. Beberapa implementasi dapat menambah/menghapus perlakuan khusus dengan menggandakan karakter di IFS (IFS=::
atau IFS=' '
).
Jadi di sini, jika kita tidak ingin karakter spasi putih awal dan akhir yang tidak lolos dihilangkan, kita perlu menghapus karakter spasi putih IFS tersebut dari IFS.
Bahkan dengan karakter IFS-non-spasi, jika baris input berisi satu (dan hanya satu) karakter tersebut dan itu adalah karakter terakhir dalam baris (seperti IFS=: read -r word
pada input seperti foo:
) dengan shell POSIX (bukan zsh
atau beberapa pdksh
versi), masukan itu dianggap sebagai satu foo
kata karena di shell itu, karakter $IFS
dianggap sebagai terminator , jadi word
akan berisi foo
, bukan foo:
.
Jadi, cara kanonik untuk membaca satu baris input dengan read
bawaannya adalah:
IFS= read -r line
(perhatikan bahwa untuk sebagian besar read
implementasi, yang hanya berfungsi untuk baris teks karena karakter NUL tidak didukung kecuali di zsh
).
Menggunakan var=value cmd
sintaks memastikan IFS
hanya disetel secara berbeda selama cmd
itu perintah.
Catatan sejarah
read
builtin diperkenalkan oleh shell Bourne dan sudah membaca kata , bukan garis. Ada beberapa perbedaan penting dengan shell POSIX modern.
read
shell Bourne tidak mendukung -r
opsi (yang diperkenalkan oleh shell Korn), jadi tidak ada cara untuk menonaktifkan pemrosesan garis miring terbalik selain memproses input sebelumnya dengan sesuatu seperti sed 's/\/&&/g'
di sana.
Shell Bourne tidak memiliki gagasan tentang dua kelas karakter (yang sekali lagi diperkenalkan oleh ksh). Di Bourne Shell semua karakter menjalani perlakuan yang sama seperti karakter spasi putih IFS di ksh, yaitu IFS=: read a b c
pada input seperti foo::bar
akan menetapkan bar
ke $b
, bukan string kosong.
Di shell Bourne, dengan:
var=value cmd
Jika cmd
adalah built-in (seperti read
adalah), var
tetap disetel ke value
setelah cmd
telah selesai. Itu sangat penting dengan $IFS
karena di shell Bourne, $IFS
digunakan untuk membagi segalanya, tidak hanya ekspansi. Juga, jika Anda menghapus karakter spasi dari $IFS
di shell Bourne, "[email protected]"
tidak lagi berfungsi.
Di shell Bourne, mengarahkan perintah gabungan menyebabkannya berjalan dalam subkulit (dalam versi paling awal, bahkan hal-hal seperti read var < file
atau exec 3< file; read var <&3
tidak bekerja), jadi di shell Bourne jarang menggunakan read
untuk apa pun kecuali input pengguna di terminal (di mana penanganan kelanjutan baris itu masuk akal)
Beberapa Unice (seperti HP/UX, ada juga di util-linux
) masih memiliki line
perintah untuk membaca satu baris input (yang dulunya merupakan perintah UNIX standar hingga Spesifikasi UNIX Tunggal versi 2).
Itu pada dasarnya sama dengan head -n 1
kecuali ia membaca satu byte pada satu waktu untuk memastikan tidak membaca lebih dari satu baris. Pada sistem tersebut, Anda dapat melakukan:
line=`line`
Tentu saja, itu berarti memunculkan proses baru, menjalankan perintah, dan membaca outputnya melalui pipa, jadi jauh lebih tidak efisien daripada IFS= read -r line
ksh , tetapi masih jauh lebih intuitif.