Tại sao việc sử dụng `yes` trên các đường ống bash * không * gây ra các vòng lặp vô hạn?


16

Theo tài liệu của nó, bash đợi cho đến khi tất cả các lệnh trong một đường ống đã chạy xong trước khi tiếp tục

Shell chờ tất cả các lệnh trong đường ống kết thúc trước khi trả về một giá trị.

Vậy tại sao lệnh yes | truekết thúc ngay lập tức? Không phải yesvòng lặp mãi mãi và khiến đường ống không bao giờ trở lại?


Và một truy vấn con: theo thông số POSIX , các đường ống vỏ có thể chọn quay trở lại sau khi lệnh cuối cùng kết thúc hoặc đợi cho đến khi tất cả các lệnh kết thúc. Các vỏ thông thường có hành vi khác nhau trong ý nghĩa này? Có vỏ nào mà yes | truevòng lặp sẽ mãi mãi?


yes | tee >(true) >/dev/nullsẽ làm như bạn mong đợi, btw, như teetiếp tục cho đến khi tất cả các nhà văn đã chết, vì vậy việc truethoát ra sẽ không phá vỡ hoàn toàn.
Charles Duffy

1
truevề cơ bản là một {return 0;}chương trình, vì vậy tôi sẽ không mong đợi nó chạy được lâu, chứ đừng nói là mãi mãi.
Dmitry Grigoryev

Câu trả lời:


33

Khi truethoát ra, phía đọc của ống được đóng lại, nhưng yestiếp tục cố gắng ghi vào bên viết. Tình trạng này được gọi là "đường ống bị hỏng" và nó khiến kernel gửi SIGPIPEtín hiệu đến yes. Vì yeskhông có gì đặc biệt về tín hiệu này, nó sẽ bị giết. Nếu nó bỏ qua tín hiệu, writecuộc gọi của nó sẽ thất bại với mã lỗi EPIPE. Các chương trình làm điều đó phải được chuẩn bị để thông báo EPIPEvà ngừng viết, nếu không chúng sẽ đi vào một vòng lặp vô hạn.

Nếu bạn làm strace yes | true1, bạn có thể thấy kernel chuẩn bị cho cả hai khả năng:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

straceđang xem các sự kiện thông qua API trình gỡ lỗi, lần đầu tiên thông báo cho nó về việc gọi lại hệ thống có lỗi và sau đó là về tín hiệu. Từ yesquan điểm 's, tuy nhiên, các tín hiệu xảy ra đầu tiên. (Về mặt kỹ thuật, tín hiệu được phân phối sau khi nhân trả lại quyền điều khiển cho không gian người dùng, nhưng trước khi thực hiện thêm bất kỳ lệnh máy nào, do đó, writechức năng "trình bao bọc" trong thư viện C không có cơ hội đặt errnovà quay lại ứng dụng.)


1 Đáng buồn thay, stracelà Linux cụ thể. Hầu hết các Unix hiện đại đều có một số lệnh làm một cái gì đó tương tự, nhưng nó thường có một tên khác, nó có thể không giải mã triệt để các đối số tòa nhà và đôi khi nó chỉ hoạt động cho root.


3
@hugomg trong trường hợp đó, đường ống hoàn toàn không liên quan.
muru

3
@hugomg vì không có gì trong yesđược kết nối với đường ống.
muru

4
Đó là sự thật, đó là một minh chứng cho hành vi được ghi lại "đợi cho đến khi tất cả các lệnh kết thúc trước khi chấm dứt đường ống". Nó chỉ ngăn không yescho SIGPIPE, vì FD nó không được kết nối với đường ống.
Tom Hunt

2
@hugomg, nó cứ lặp đi lặp lại theo cùng một cách yes >/dev/nulllặp đi lặp lại mãi mãi. Nó không chứng minh gì cả về các đường ống cũng không đúng với các lệnh đơn giản (vì hành vi chờ kết thúc mà Tom chỉ ra cũng đúng với các lệnh đơn giản).
Charles Duffy

2
@zwol: Tôi nghĩ rằng chúng tôi đang sử dụng các thuật ngữ của chúng tôi với ý nghĩa hơi khác nhau ở đây hoặc suy nghĩ về những điều từ các quan điểm hơi khác nhau nhưng trong cả hai trường hợp, write()(chức năng trong libc) không trở lại (chuyển điều khiển sang PC theo sau) trình xử lý tín hiệu đã chạy, nhưng vì trình xử lý tín hiệu kết thúc chương trình, điều khiển không bao giờ được chuyển và do đó write()không bao giờ quay trở lại. Vâng, điều đó được triển khai trong kernel bằng cách xxx_write()trả lại một số chức năng -EPIPE, nhưng chúng tôi đang gỡ lỗi một chương trình không gian người dùng và không quan tâm đến điều đó.
Dietrich Epp

5

Có vỏ nào có không | Sự thật sẽ lặp lại mãi mãi?

Không thể, vì yeslệnh đang sử dụng đường ống, và nó sẽ thất bại khi đường ống bị hỏng. sleepmặt khác, không sử dụng đường ống, vì vậy:

sleep 100000000 | true

sẽ chạy trong 100000000 giây, ít nhất.


2
Cẩn thận với tất cả các shell hiện đại không rẽ nhánh cho lệnh dựng sẵn cuối cùng (ngoài cùng bên phải) trong một đường ống và nơi trueđược dựng sẵn. Này áp dụng cho các phiên bản gần đây của Bourne Shell, ksh93, zsh. Nếu bạn nhấn ^Zkhi một lệnh như vậy đang chạy, điều này sẽ tạm dừng chế độ ngủ và shell sẽ không bao giờ có thể phục hồi nếu không có sự trợ giúp bên ngoài.
schily

3
zsh 4.3.4 (i386-pc-solaris2.11) ở đây, vì vậy có vẻ như điều này đã được sửa đổi gần đây. Ý tưởng thú vị, tôi sẽ cần phải xem liệu tôi có thể thực hiện một bản sửa lỗi tương tự cho Bourne Shell hay không. Vẫn còn một câu hỏi về cách thức hoạt động của nó và nhóm quy trình tty nào được sử dụng như trong Bourne Shell, thực tế là lệnh cao nhất là một nội dung được phát hiện sau khi nhóm quy trình cho giấc ngủ đã được thiết lập mãi mãi.
schily

2
@CharlesDuffy từ những gì tôi hiểu, schily duy trì một phiên bản của sh, mà anh ấy ủng hộ các cải tiến từ vỏ hiện đại. Anh ấy đã đăng về nó ở đây, một nơi nào đó.
muru

3
Shell Bourne trong kho lưu trữ gia truyền được duy trì cho đến năm 2007 nhưng không bao giờ được thực hiện hoàn toàn di động vì nó vẫn chứa các cuộc gọi đến sbrk(). Một phiên bản di động và được bảo trì nằm trong gói công cụ schily và @Charles Duffy đã phát hiện ra một vị trí để biết thông tin ;-)
schily

2
@muru nhiều tính năng tôi nhập vào Bourne Shell là từ bsh(Berthold Shell từ VBERTOS, phiên bản nâng cao bộ nhớ ảo của UNOS - bản sao UNIX đầu tiên). Bsh đã nhận được nhiều tính năng csh vào năm 1984 và 1985 nhưng cơ chế bí danh từ UNOS đã vượt trội so với csh từ năm 1980. Các tính năng mới khác của Bourne Shell là từ POSIX, để cho phép nó tiếp cận tuân thủ POSIX.
schily
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.