Tại sao có một điều kiện cuộc đua
Hai bên của một đường ống được thực hiện song song, không phải lần lượt từng bên. Có một cách rất đơn giản để chứng minh điều này: chạy
time sleep 1 | sleep 1
Điều này mất một giây, không phải hai.
Shell bắt đầu hai quá trình con và chờ cho cả hai hoàn thành. Hai quá trình này thực thi song song: lý do duy nhất tại sao một trong số chúng sẽ đồng bộ hóa với cái kia là khi nó cần chờ cho cái kia. Điểm đồng bộ hóa phổ biến nhất là khi các khối bên phải chờ dữ liệu đọc trên đầu vào tiêu chuẩn của nó và bị bỏ chặn khi phía bên trái ghi nhiều dữ liệu hơn. Điều ngược lại cũng có thể xảy ra, khi phía bên phải đọc dữ liệu chậm và các khối bên trái trong hoạt động ghi của nó cho đến khi phía bên phải đọc thêm dữ liệu (có một bộ đệm trong chính đường ống, được quản lý bởi kernel, nhưng nó có kích thước tối đa nhỏ).
Để quan sát điểm đồng bộ hóa, hãy quan sát các lệnh sau ( sh -x
in từng lệnh khi thực thi):
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
Chơi với các biến thể cho đến khi bạn cảm thấy thoải mái với những gì bạn quan sát.
Đưa ra lệnh ghép
cat tmp | head -1 > tmp
quy trình bên trái thực hiện như sau (Tôi chỉ liệt kê các bước có liên quan đến giải thích của tôi):
- Thực hiện chương trình bên ngoài
cat
với các đối số tmp
.
- Mở
tmp
để đọc.
- Trong khi nó chưa đến cuối tập tin, hãy đọc một đoạn từ tệp và ghi nó vào đầu ra tiêu chuẩn.
Quá trình bên tay phải thực hiện như sau:
- Chuyển hướng đầu ra tiêu chuẩn đến
tmp
, cắt bớt tệp trong quy trình.
- Thực hiện chương trình bên ngoài
head
với các đối số -1
.
- Đọc một dòng từ đầu vào tiêu chuẩn và ghi nó vào đầu ra tiêu chuẩn.
Điểm duy nhất của đồng bộ hóa là phải 3 chờ trái 3 để xử lý một dòng đầy đủ. Không có sự đồng bộ giữa trái-2 và phải-1, vì vậy chúng có thể xảy ra theo thứ tự. Thứ tự chúng xảy ra không thể dự đoán được: nó phụ thuộc vào kiến trúc CPU, vỏ, nhân, trên đó các lõi xảy ra theo lịch trình, vào những gì làm gián đoạn CPU nhận được trong khoảng thời gian đó, v.v.
Cách thay đổi hành vi
Bạn không thể thay đổi hành vi bằng cách thay đổi cài đặt hệ thống. Máy tính làm những gì bạn bảo nó làm. Bạn bảo nó cắt ngắn tmp
và đọc tmp
song song, vì vậy nó làm hai việc song song.
Ok, có một hệ thống cài đặt khác mà bạn có thể thay đổi: bạn có thể thay thế /bin/bash
bằng một chương trình khác không phải là bash. Tôi hy vọng nó sẽ đi mà không nói rằng đây không phải là một ý tưởng tốt.
Nếu bạn muốn cắt ngắn xảy ra trước phía bên trái của đường ống, bạn cần đặt nó bên ngoài đường ống, ví dụ:
{ cat tmp | head -1; } >tmp
hoặc là
( exec >tmp; cat tmp | head -1 )
Tôi không biết tại sao bạn muốn điều này mặc dù. Điểm nào trong việc đọc từ một tệp mà bạn biết là trống?
Ngược lại, nếu bạn muốn chuyển hướng đầu ra (bao gồm cả cắt ngắn) xảy ra sau khi cat
đọc xong, thì bạn cần phải đệm đầy đủ dữ liệu trong bộ nhớ, ví dụ:
line=$(cat tmp | head -1)
printf %s "$line" >tmp
hoặc ghi vào một tệp khác và sau đó di chuyển nó vào vị trí. Đây thường là cách mạnh mẽ để thực hiện mọi thứ trong tập lệnh và có lợi thế là tập tin được viết đầy đủ trước khi nó hiển thị thông qua tên gốc.
cat tmp | head -1 >new && mv new tmp
Bộ sưu tập moreutils bao gồm một chương trình thực hiện điều đó, được gọi là sponge
.
cat tmp | head -1 | sponge tmp
Cách phát hiện vấn đề tự động
Nếu mục tiêu của bạn là lấy các kịch bản được viết xấu và tự động tìm ra nơi chúng bị hỏng, thì xin lỗi, cuộc sống không đơn giản như vậy. Phân tích thời gian chạy sẽ không đáng tin cậy tìm thấy vấn đề bởi vì đôi khi cat
kết thúc việc đọc trước khi cắt ngắn xảy ra. Phân tích tĩnh về nguyên tắc có thể làm điều đó; ví dụ đơn giản hóa trong câu hỏi của bạn được Shellcheck nắm bắt, nhưng nó có thể không gặp vấn đề tương tự trong một tập lệnh phức tạp hơn.