มันไม่ใช่แค่ echo vs printf
ก่อนอื่นเรามาทำความเข้าใจว่าเกิดอะไรขึ้นกับread a b c
ส่วน read
จะดำเนินการแยกคำตามค่าเริ่มต้นของIFS
ตัวแปรซึ่งเป็น space-tab-newline และพอดีทุกอย่างตามที่ หากมีอินพุตมากกว่าตัวแปรที่จะเก็บไว้มันจะใส่ส่วนที่แยกออกเป็นตัวแปรแรกและสิ่งที่ไม่สามารถติดตั้งได้ - จะเข้าสู่ช่วงสุดท้าย นี่คือสิ่งที่ฉันหมายถึง:
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
นี่เป็นวิธีที่อธิบายไว้ในbash
คู่มือของ (ดูเครื่องหมายคำพูดท้ายคำตอบ)
ในกรณีของคุณสิ่งที่เกิดขึ้นคือการที่ 1 และ 2 พอดีและ b ตัวแปรและ c 3 4 5 6
ใช้เวลาทุกอย่างอื่นซึ่งเป็น
สิ่งที่คุณจะเห็นบ่อยครั้งคือผู้คนใช้while IFS= read -r line; do ... ; done < input.txt
อ่านไฟล์ข้อความทีละบรรทัด อีกครั้งIFS=
อยู่ที่นี่เพื่อเหตุผลในการควบคุมการแยกคำหรือเฉพาะเจาะจงมากขึ้น - ปิดการใช้งานมันและอ่านข้อความบรรทัดเดียวในตัวแปร หากไม่มีอยู่read
ให้ลองปรับคำแต่ละคำให้เป็นline
ตัวแปร แต่นั่นเป็นอีกเรื่องหนึ่งที่ฉันแนะนำให้คุณศึกษาในภายหลังเนื่องจากwhile IFS= read -r variable
เป็นโครงสร้างที่ใช้บ่อยมาก
echo vs printf พฤติกรรม
echo
ทำในสิ่งที่คุณคาดหวังที่นี่ จะแสดงตัวแปรของคุณตรงตามที่read
ได้จัดไว้ สิ่งนี้แสดงให้เห็นแล้วในการสนทนาก่อนหน้านี้
printf
มีความพิเศษมากเพราะมันจะเก็บตัวแปรที่เหมาะสมไว้ในรูปแบบของสตริงจนกว่าพวกมันจะหมด ดังนั้นเมื่อคุณพิมพ์printf "%d, %d, %d \n" $a $b $c
printf เห็นสตริงรูปแบบที่มี 3 ทศนิยม แต่มีข้อโต้แย้งมากกว่า 3 (เพราะตัวแปรของคุณขยายไปถึง 1,2,3,4,5,6 จริง ๆ ) สิ่งนี้อาจฟังดูสับสน แต่มีเหตุผลเนื่องจากพฤติกรรมที่ดีขึ้นจากสิ่งที่ฟังก์ชั่นจริง printf()
ทำในภาษา C
สิ่งที่คุณทำที่นี่ที่มีผลต่อผลลัพธ์คือตัวแปรของคุณไม่ได้ถูกอ้างถึงซึ่งทำให้เชลล์ (ไม่printf
) แบ่งตัวแปรออกเป็น 6 รายการแยกกัน เปรียบเทียบกับข้อความ:
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
เนื่องจาก$c
ตัวแปรถูกยกมาตอนนี้จะรับรู้เป็นสตริงทั้งหมด3 4
และไม่พอดีกับ%d
รูปแบบซึ่งเป็นเพียงจำนวนเต็มเดียว
ตอนนี้ทำเช่นเดียวกันโดยไม่ต้องอ้างถึง:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
อีกครั้งบอกว่า: "ตกลงคุณมี 6 รายการที่นั่น แต่รูปแบบแสดงเพียง 3 ดังนั้นฉันจะเก็บสิ่งที่เหมาะสมและปล่อยให้ว่างเปล่าสิ่งที่ฉันไม่สามารถจับคู่กับการป้อนข้อมูลที่แท้จริงจากผู้ใช้"
และในกรณีเหล่านี้คุณไม่ต้องใช้คำพูดของฉัน เพียงแค่เรียกใช้strace -e trace=execve
และดูด้วยตัวคุณเองว่าคำสั่งอะไรจริง "ดู":
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 +++
หมายเหตุเพิ่มเติม
ดังที่ Charles Duffy ชี้ให้เห็นอย่างถูกต้องในความคิดเห็นbash
มีตัวของมันเองprintf
ซึ่งเป็นสิ่งที่คุณใช้ในคำสั่งของคุณstrace
จะเรียก/usr/bin/printf
เวอร์ชันจริง ๆไม่ใช่เวอร์ชันของเชลล์ นอกเหนือจากความแตกต่างเล็กน้อยสำหรับความสนใจของเราในคำถามนี้ตัวระบุรูปแบบมาตรฐานจะเหมือนกันและพฤติกรรมจะเหมือนกัน
สิ่งที่ควรคำนึงถึงก็คือprintf
ไวยากรณ์นั้นพกพาได้มากกว่า (และเป็นที่ต้องการมากกว่า) echo
ไม่ต้องพูดถึงว่าไวยากรณ์นั้นคุ้นเคยกับ C หรือภาษา C-like ใด ๆ ที่มีprintf()
ฟังก์ชันอยู่มากกว่า เห็นนี้คำตอบที่ดีเยี่ยมโดย terdonในเรื่องของVSprintf
echo
ในขณะที่คุณสามารถปรับแต่งเอาต์พุตให้เหมาะกับเชลล์เฉพาะของคุณบน Ubuntu เฉพาะรุ่นของคุณหากคุณกำลังจะย้ายสคริปต์ไปยังระบบต่าง ๆ คุณควรจะชอบprintf
มากกว่าเสียงก้อง บางทีคุณอาจเป็นผู้ดูแลระบบมือใหม่ที่ทำงานกับเครื่อง Ubuntu และ CentOS หรืออาจเป็น FreeBSD ใครจะรู้ - ดังนั้นในกรณีเช่นนี้คุณจะต้องเลือก
อ้างอิงจากคู่มือทุบตีส่วนคำสั่งของเชลล์สร้าง
อ่าน [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-p หมดเวลา] [-t หมดเวลา] [-u fd] [ชื่อ ... ]
หนึ่งบรรทัดถูกอ่านจากอินพุตมาตรฐานหรือจากไฟล์ descriptor fd ที่ระบุเป็นอาร์กิวเมนต์สำหรับตัวเลือก -u และคำแรกถูกกำหนดให้กับชื่อแรกคำที่สองไปยังชื่อที่สองและอื่น ๆ ที่เหลือ คำและส่วนที่คั่นด้วยการแทรกแซงของพวกเขาได้รับมอบหมายให้ใช้นามสกุล หากมีคำที่อ่านจากอินพุตสตรีมน้อยกว่าชื่อชื่อที่เหลือจะถูกกำหนดค่าว่าง อักขระใน IFS ใช้เพื่อแยกบรรทัดเป็นคำโดยใช้กฎเดียวกับที่เชลล์ใช้สำหรับการขยาย (อธิบายไว้ข้างต้นภายใต้การแยกคำ)
strace
เคสกับอีกอย่างstrace printf
คือใช้/usr/bin/printf
ในขณะที่printf
โดยตรงใน bash คือการใช้เชลล์ builtin ในชื่อเดียวกัน พวกเขาจะไม่เหมือนกัน - ตัวอย่างเช่น bash มีตัวระบุรูปแบบ%q
และในเวอร์ชันใหม่$()T
สำหรับการจัดรูปแบบเวลา