Ini bukan hanya gema vs printf
Pertama, mari kita pahami apa yang terjadi dengan read a b cbagian itu. readakan melakukan pemisahan kata berdasarkan nilai default IFSvariabel yaitu spasi-tab-baris baru, dan sesuai dengan segalanya berdasarkan itu. Jika ada lebih banyak input daripada variabel untuk menahannya, itu akan cocok bagian yang dibagi menjadi variabel pertama, dan apa yang tidak bisa dipasang - akan masuk ke yang terakhir. Inilah yang saya maksud:
bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four
Ini persis seperti yang dijelaskan dalam bashmanual (lihat kutipan di akhir jawaban).
Dalam kasus Anda yang terjadi adalah bahwa, 1 dan 2 masuk ke dalam variabel a dan b, dan c mengambil yang lainnya, yaitu 3 4 5 6.
Apa yang Anda juga akan sering lihat adalah yang digunakan orang while IFS= read -r line; do ... ; done < input.txtuntuk membaca file teks baris demi baris. Sekali lagi, IFS=ada di sini karena alasan untuk mengontrol pemisahan kata, atau lebih khusus - menonaktifkannya, dan membaca satu baris teks menjadi variabel. Jika tidak ada di sana, readakan mencoba mencocokkan setiap kata menjadi linevariabel. Tapi itu cerita lain, yang saya anjurkan Anda untuk pelajari nanti, karena while IFS= read -r variablemerupakan struktur yang sangat sering digunakan.
perilaku echo vs printf
echomelakukan apa yang Anda harapkan di sini. Ini menampilkan variabel Anda persis seperti yang readtelah mereka atur. Ini sudah ditunjukkan dalam diskusi sebelumnya.
printfsangat istimewa, karena akan terus memasukkan variabel ke dalam format string sampai semuanya habis. Jadi ketika Anda melakukan printf "%d, %d, %d \n" $a $b $cprintf melihat format string dengan 3 desimal, tetapi ada lebih banyak argumen dari 3 (karena variabel Anda benar-benar berkembang menjadi 1,2,3,4,5,6 individu). Ini mungkin terdengar membingungkan, tetapi ada karena alasan peningkatan perilaku dari apa fungsi sebenarnya printf() tidak dalam bahasa C.
Apa yang Anda lakukan di sini yang mempengaruhi output adalah bahwa variabel Anda tidak dikutip, yang memungkinkan shell (tidak printf) memecah variabel menjadi 6 item terpisah. Bandingkan ini dengan mengutip:
bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3
Persis karena $cvariabel dikutip, sekarang diakui sebagai satu string keseluruhan 3 4,, dan tidak cocok dengan %dformat, yang hanya merupakan integer tunggal
Sekarang lakukan hal yang sama tanpa mengutip:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf sekali lagi mengatakan: "OK, Anda memiliki 6 item di sana tetapi format hanya menunjukkan 3, jadi saya akan menyimpan barang dan membiarkan apa pun yang saya tidak cocok dengan input sebenarnya dari pengguna".
Dan dalam semua kasus ini Anda tidak harus mengambil kata saya untuk itu. Jalankan strace -e trace=execvedan lihat sendiri apa yang sebenarnya perintah "lihat":
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: ‘3 4’: value not completely converted
3
+++ exited with 1 +++
Catatan tambahan
Seperti yang ditunjukkan Charles Duffy dengan benar di komentar, bashmemiliki built-in sendiri printf, yang Anda gunakan dalam perintah Anda, stracesebenarnya akan memanggil /usr/bin/printfversi, bukan versi shell. Selain perbedaan kecil, untuk minat kami dalam pertanyaan khusus ini penentu format standar adalah sama dan perilaku adalah sama.
Yang juga harus diingat adalah bahwa printfsintaks jauh lebih portabel (dan karena itu lebih disukai) daripada echo, belum lagi bahwa sintaksis lebih akrab dengan C atau bahasa seperti C yang memiliki printf()fungsi di dalamnya. Lihat ini jawaban yang sangat baik dengan Terdon pada subjek printfvs echo. Meskipun Anda dapat membuat output yang disesuaikan dengan shell spesifik Anda pada versi spesifik Ubuntu Anda, jika Anda akan mem-porting skrip di berbagai sistem, Anda mungkin harus memilih printfdaripada gema. Mungkin Anda seorang administrator sistem pemula yang bekerja dengan mesin Ubuntu dan CentOS, atau mungkin bahkan FreeBSD - siapa tahu - jadi dalam kasus seperti itu Anda harus membuat pilihan.
Kutipan dari bash manual, bagian SHELL BUILTIN COMMANDS
baca [-ers] [-a aname] [-d delim] [-i teks] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nama ... ]
Satu baris dibaca dari input standar, atau dari deskriptor file yang disediakan sebagai argumen ke opsi -u, dan kata pertama ditugaskan ke nama depan, kata kedua ke nama kedua, dan seterusnya, dengan sisa kata-kata dan pemisah intervensi mereka ditugaskan untuk nama belakang. Jika ada lebih sedikit kata yang dibaca dari aliran input dari nama, nama yang tersisa diberikan nilai kosong. Karakter dalam IFS digunakan untuk membagi baris menjadi kata-kata menggunakan aturan yang sama yang digunakan shell untuk ekspansi (dijelaskan di bawah di bawah Word Splitting).
stracekasing dan kasing lainnya -strace printfmenggunakan/usr/bin/printf, sedangkanprintflangsung di bash menggunakan shell yang dibangun dengan nama yang sama. Mereka tidak akan selalu identik - misalnya, contoh bash memiliki penentu format%qdan, dalam versi baru,$()Tuntuk pemformatan waktu.