Nó không chỉ là echo vs printf
Đầu tiên, hãy hiểu những gì xảy ra với read a b c
một phần. read
sẽ thực hiện phân tách từ dựa trên giá trị mặc định của IFS
biến là không gian-tab-newline và phù hợp với mọi thứ dựa trên đó. Nếu có nhiều đầu vào hơn các biến để giữ nó, nó sẽ khớp các phần được chia thành các biến đầu tiên và những gì không thể được trang bị - sẽ đi vào cuối cùng. Ý tôi là đây:
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
Đây chính xác là cách nó được mô tả trong bash
hướng dẫn sử dụng (xem trích dẫn ở cuối câu trả lời).
Trong trường hợp của bạn, điều xảy ra là, 1 và 2 khớp với biến a và b, và c lấy mọi thứ khác, đó là 3 4 5 6
.
Những gì bạn cũng sẽ thấy rất nhiều lần là mọi người sử dụng while IFS= read -r line; do ... ; done < input.txt
để đọc các tệp văn bản theo từng dòng. Một lần nữa, IFS=
đây là lý do để kiểm soát việc tách từ, hay cụ thể hơn - vô hiệu hóa nó và đọc một dòng văn bản thành một biến. Nếu nó không ở đó, read
sẽ cố gắng ghép từng từ riêng lẻ thành line
biến. Nhưng đó là một câu chuyện khác, mà tôi khuyến khích bạn nghiên cứu sau này, vì đây while IFS= read -r variable
là một cấu trúc được sử dụng rất thường xuyên.
hành vi echo vs printf
echo
làm những gì bạn mong đợi ở đây. Nó hiển thị các biến của bạn chính xác như read
đã sắp xếp chúng. Điều này đã được chứng minh trong các cuộc thảo luận trước đây.
printf
là rất đặc biệt, bởi vì nó sẽ tiếp tục điều chỉnh các biến thành chuỗi định dạng cho đến khi tất cả chúng bị cạn kiệt. Vì vậy, khi bạn in printf "%d, %d, %d \n" $a $b $c
printf thấy chuỗi định dạng có 3 số thập phân, nhưng có nhiều đối số hơn 3 (vì các biến của bạn thực sự mở rộng thành 1,2,3,4,5,6 riêng lẻ). Điều này nghe có vẻ khó hiểu, nhưng tồn tại vì một lý do là hành vi được cải thiện so với những gì chức năng thực sự printf()
làm trong ngôn ngữ C.
Điều bạn cũng đã làm ở đây ảnh hưởng đến đầu ra là các biến của bạn không được trích dẫn, điều này cho phép trình bao (không printf
) chia nhỏ các biến thành 6 mục riêng biệt. So sánh điều này với trích dẫn:
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
Chính xác bởi vì $c
biến được trích dẫn, giờ đây nó được nhận dạng là một chuỗi toàn bộ 3 4
và nó không phù hợp với %d
định dạng, chỉ là một số nguyên duy nhất
Bây giờ làm tương tự mà không cần trích dẫn:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
một lần nữa nói: "OK, bạn có 6 mục ở đó nhưng định dạng chỉ hiển thị 3, vì vậy tôi sẽ tiếp tục lắp đồ đạc và để trống bất cứ thứ gì tôi không thể khớp với đầu vào thực tế từ người dùng".
Và trong tất cả các trường hợp này, bạn không cần phải tin lời tôi. Chỉ cần chạy strace -e trace=execve
và xem cho chính mình những gì thực sự lệnh "thấy":
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 +++
Ghi chú bổ sung
Như Charles Duffy đã chỉ ra một cách chính xác trong các bình luận, bash
có tích hợp riêng printf
, đó là những gì bạn đang sử dụng trong lệnh của mình, strace
sẽ thực sự gọi /usr/bin/printf
phiên bản, không phải phiên bản của shell. Ngoài những khác biệt nhỏ, đối với mối quan tâm của chúng tôi đối với câu hỏi cụ thể này, các chỉ định định dạng chuẩn là giống nhau và hành vi là như nhau.
Điều cần lưu ý là printf
cú pháp dễ mang theo hơn (và do đó được ưa thích hơn) echo
, chưa kể rằng cú pháp quen thuộc hơn với C hoặc bất kỳ ngôn ngữ giống C nào có printf()
chức năng trong đó. Xem này câu trả lời tuyệt vời bởi terdon về vấn đề printf
vs echo
. Mặc dù bạn có thể làm cho đầu ra phù hợp với vỏ cụ thể của bạn trên phiên bản Ubuntu cụ thể của bạn, nếu bạn sẽ chuyển các tập lệnh trên các hệ thống khác nhau, có lẽ bạn nên thích printf
hơn là tiếng vang. Có thể bạn là quản trị viên hệ thống mới bắt đầu làm việc với các máy Ubuntu và CentOS, hoặc thậm chí FreeBSD - người biết - vì vậy trong những trường hợp như vậy, bạn sẽ phải đưa ra lựa chọn.
Trích dẫn từ hướng dẫn bash, phần SHELL BUILTIN
đọc [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]
Một dòng được đọc từ đầu vào tiêu chuẩn hoặc từ bộ mô tả tệp fd được cung cấp làm đối số cho tùy chọn -u và từ đầu tiên được gán cho tên đầu tiên, từ thứ hai cho tên thứ hai, v.v. các từ và các dấu phân cách can thiệp của chúng được gán cho tên cuối cùng. Nếu có ít từ được đọc từ luồng đầu vào hơn tên, các tên còn lại được gán giá trị trống. Các ký tự trong IFS được sử dụng để phân chia dòng thành các từ bằng cách sử dụng cùng một quy tắc mà trình bao sử dụng để mở rộng (được mô tả ở trên trong Phân tách từ).
strace
trường hợp này và trường hợp khác -strace printf
đang sử dụng/usr/bin/printf
, trong khiprintf
trực tiếp trong bash là sử dụng shell được dựng cùng tên. Chúng sẽ không giống nhau - ví dụ, ví dụ bash có các bộ định dạng định dạng%q
và, trong các phiên bản mới,$()T
để định dạng thời gian.