Sử dụng trên mạng trong khi đọc trên mạng, tiếng vang và printf nhận được các kết quả khác nhau


14

Theo câu hỏi này " Sử dụng" trong khi đọc ... "trong tập lệnh linux "

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

kết quả:

1, 2, 3 4 5 6

nhưng khi tôi thay thế echobằngprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

kết quả

1, 2, 3
4, 5, 6

Ai đó có thể vui lòng cho tôi biết những gì làm cho hai lệnh này khác nhau? Cảm ơn

Câu trả lời:


24

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 cmột phần. readsẽ thực hiện phân tách từ dựa trên giá trị mặc định của IFSbiế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 bashhướ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 ở đó, readsẽ cố gắng ghép từng từ riêng lẻ thành linebiế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 variablelà một cấu trúc được sử dụng rất thường xuyên.

hành vi echo vs printf

echolà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.

printflà 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 $cprintf 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ì $cbiến được trích dẫn, giờ đây nó được nhận dạng là một chuỗi toàn bộ 3 4và 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=execvevà 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, bashcó tích hợp riêng printf, đó là những gì bạn đang sử dụng trong lệnh của mình, stracesẽ thực sự gọi /usr/bin/printfphiê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à printfcú 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 đề printfvs 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 printfhơ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ừ).


2
Một sự khác biệt đáng chú ý giữa stracetrường hợp này và trường hợp khác - strace printfđang sử dụng /usr/bin/printf, trong khi printftrự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 %qvà, trong các phiên bản mới, $()Tđể định dạng thời gian.
Charles Duffy

7

Đây chỉ là một gợi ý và không nhằm thay thế câu trả lời của Sergiy. Tôi nghĩ Sergiy đã viết một câu trả lời tuyệt vời về lý do tại sao chúng khác nhau trong in ấn. Làm thế nào biến trên đọc được gán với phần còn lại vào $cbiến như 3 4 5 6sau 12được gán cho ab. echosẽ không phân chia biến cho bạn khi nào printfsẽ với %ds.

Tuy nhiên, bạn có thể làm cho chúng về cơ bản cung cấp cho bạn các câu trả lời tương tự bằng cách điều khiển tiếng vang của các số ở đầu lệnh:

Trong /bin/bashbạn có thể sử dụng:

echo -e "1 2 3 \n4 5 6"

Trong /bin/shbạn chỉ có thể sử dụng:

echo "1 2 3 \n4 5 6"

Bash sử dụng -e để kích hoạt các ký tự \ esc trong đó sh không cần nó vì nó đã được kích hoạt. \nlàm cho nó tạo ra một dòng mới, vì vậy bây giờ dòng echo được chia thành hai dòng riêng biệt có thể được sử dụng hai lần cho câu lệnh lặp echo của bạn:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

Lần lượt tạo ra cùng một đầu ra với việc sử dụng printflệnh:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Trong sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Hi vọng điêu nay co ich!


echo -ekhông chỉ là một phần mở rộng cho POSIX - mà bất kỳ phần mở rộng nào echokhông phát ra -etrên đầu ra của nó đều cho rằng đầu vào thực sự đang thách thức / phá vỡ đặc tả chữ đen. (bash không tuân thủ theo mặc định, nhưng tuân thủ tiêu chuẩn khi cả hai posixxpg_echocờ được đặt; cái sau có thể được mặc định tại thời gian biên dịch, có nghĩa là không phải tất cả các phiên bản bash sẽ hoạt động echo -enhư bạn mong đợi ở đây).
Charles Duffy

Xem các phần SỬ DỤNG và RATIONALE ỨNG DỤNG của thông số POSIX echotại pubs.opengroup.org/onlinepub/9699919799/utilities/echo.html , mô tả rõ ràng rằng echohành vi của nó không được chỉ định khi có mặt -nhoặc dấu gạch chéo ở bất cứ đâu trong đầu vào và khuyên printfđược sử dụng thay thế.
Charles Duffy

@CharlesDuffy Tôi đã bao giờ viết trong tôi rằng tôi hy vọng nó sẽ hoạt động trong tất cả các phiên bản của bash? Và bạn có vấn đề gì với câu trả lời của tôi? Nếu bạn cảm thấy mình có giải pháp tốt hơn, hãy viết nó như một câu trả lời thay vì cố gắng chứng minh rằng mọi người đã sai. Tôi đã trải qua cách này quá nhiều lần với những người như bạn, vì vậy hãy dừng lại với những bình luận như thế này. Đó là tất cả những gì tôi phải nói.
Terrance

3
@CharlesDuffy trong Hỏi Ubuntu đôi khi chúng tôi viết câu trả lời không thể chuyển sang các bản phân phối Linux khác. Vì đại diện của bạn ở đây chỉ có 103 điểm nên bạn có thể không nhận thấy điều đó. Đại diện của bạn trên Stack Overflow mặc dù là 123,3K điểm nên rõ ràng bạn biết rất nhiều thứ về các hệ thống * NIX nói chung. Tôi nghĩ rằng You, Terrace và Serg đều ổn nhưng đang nhìn vào sợi chỉ từ các góc độ khác nhau. Tôi lặp lại (không có ý định chơi chữ) tình cảm để bạn viết một câu trả lời là * NIX xách tay, hoặc ít nhất là di động trên các bản phân phối Linux.
WinEunuuchs2Unix

2
@CharlesDuffy Mặc dù tôi đồng ý với các câu trả lời về tình cảm của bạn nên được thực hiện, nhưng cuối cùng, đây là trang web hướng đến Ubuntu, do đó người dùng nên sử dụng các công cụ dành riêng cho Ubuntu. Vì vậy, cảnh báo có phần ngụ ý. Chúng ta đừng đi vào các cuộc thảo luận quá dài. Bạn đúng rằng các vỏ khác nên được xem xét và những người mới đọc để tìm hiểu từ câu trả lời cũng nên được xem xét. Một bình luận ngắn có lẽ sẽ đủ để đưa ra quan điểm.
Sergiy Kolodyazhnyy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.