ống, {danh sách; } chỉ hoạt động với một số chương trình


13

Cần giải thích từ người dùng quyền lực cho hành vi không thể đoán trước như vậy:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

mọi thứ đều ổn

ls -la / | { head -n 1;grep sbin; }

chỉ hiển thị đầu ra từ head

... Tôi đã nghĩ stdout 2>&1và không làm việc cho tôi, điều đó thật kỳ lạ, có lời giải thích hay đề nghị nào để xử lý nó không?


1
Người cuối cùng nên in ra tất cả mọi thứ. Các headgreplàm gì ở đó.
jordanm

vâng, bạn đúng Nhưng thay vì nó, tại sao ps -eF hoạt động trong khi ls -la / không?
ast

Câu trả lời:


9

Tôi đã thực hiện một số điều tra bằng cách sử dụng stracevà dường như là do cách chương trình ở phía bên trái của đường ống dẫn đến thiết bị đầu cuối. Khi lslệnh được thực thi, nó ghi tất cả dữ liệu trong một write(). Điều này gây ra headtiêu thụ tất cả các stdin.

Mặt khác, psghi dữ liệu theo lô, vì vậy chỉ có dữ liệu đầu tiên write()được sử dụng headvà sau đó nó tồn tại. Các cuộc gọi sau write()này sẽ đi đến grepquá trình mới được sinh ra .

Điều này có nghĩa là nó sẽ không hoạt động nếu quá trình bạn đang cố gắng grepkhông xảy ra trong lần đầu tiên write(), vì grepkhông thể xem tất cả dữ liệu (nó thậm chí còn nhìn thấy ít hơn chỉ là dữ liệu trừ dòng đầu tiên).

Đây là một ví dụ về việc cố gắng grep cho pid 1 trên hệ thống của tôi:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

ps -eFVí dụ của bạn chỉ hoạt động tình cờ.


đánh giá cao và toàn diện cảm ơn rất nhiều
ast

1
Trên thực tế, đó là một điều kiện cuộc đua. Chỉ là nó thực hiện chậm hơn nhiều write()cuộc gọi. Nếu headchậm thực hiện read()cuộc gọi của nó (như bộ đệm ống có tất cả dữ liệu trong đó), thì nó sẽ thể hiện hành vi tương tự trên cả hai lsps.
Patrick

6

Điều này được gây ra bởi bộ đệm trong glibc. Trong trường hợp lsđầu ra là trong một bộ đệm nội bộ và như vậy được truyền vào head. Đối với ps -eF, đầu ra lớn hơn và vì vậy một khi headkết thúc, phần sau grepsẽ lấy các phần còn lại của (nhưng không phải toàn bộ) đầu ra của ps.

Bạn có thể thoát khỏi nó bằng cách bỏ đệm ống - ví dụ với sed -u(tôi không chắc nó không phải là phần mở rộng GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin

4

Điều gì đang xảy ra là head -n 1đọc nhiều hơn 1 dòng. Để có thông lượng tối ưu, đầu đọc các đoạn byte, do đó, nó có thể đọc 1024 byte mỗi lần, và sau đó xem qua các byte đó để ngắt dòng đầu tiên. Vì ngắt dòng có thể xảy ra ở giữa 1024 byte đó, phần còn lại của dữ liệu sẽ bị mất. Nó không thể được đặt trở lại trên đường ống. Vì vậy, quá trình tiếp theo thực thi chỉ nhận được byte 1025 trở đi.

Lệnh đầu tiên của bạn xảy ra thành công vì kworkerquá trình là sau đoạn đầu tiên mà headđọc.

Để làm việc này, headsẽ phải đọc 1 ký tự một lần. Nhưng điều này là cực kỳ chậm, vì vậy nó không.
Cách duy nhất để làm một cái gì đó như thế này một cách hiệu quả là có một quy trình duy nhất làm cả "đầu" và "grep".

Dưới đây là 2 cách để làm điều này:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

hoặc là

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

Còn nhiều nữa ...


vâng, tôi đã biết 'cách thức của awk' để xử lý công việc này, nhưng đã tự hỏi tại sao hành vi lại khó đoán với {list; }. Cảm ơn đã làm rõ cách thức hoạt động. Tôi ấn tượng với tất cả các câu trả lời ở trên
ast 18/10/13

2

Nếu bạn chỉ muốn dòng đầu tiên hoặc hai, loại thủ thuật sau hoạt động và tránh các vấn đề về bộ đệm gây ra bằng cách sử dụng hai lệnh khác nhau để đọc luồng đầu ra:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

readđược tích hợp vào trình bao và không tiêu thụ toàn bộ bộ đệm đầu vào chỉ để xuất một dòng, do đó, sử dụng readđể lại tất cả phần còn lại của đầu ra cho lệnh sau.

Nếu bạn muốn làm nổi bật các vấn đề về bộ đệm được hiển thị bằng các ví dụ của bạn sử dụng hai lệnh khác nhau, hãy thêm một sleepđể xử lý các vấn đề về thời gian và cho phép lệnh bên trái tạo ra tất cả đầu ra của nó trước khi các lệnh bên phải cố gắng đọc bất kỳ nó:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

Bây giờ, cả hai ví dụ trên đều thất bại theo cùng một cách - headđọc toàn bộ bộ đệm của đầu ra chỉ để tạo ra một dòng và bộ đệm đó không có sẵn cho các mục sau grep.

Bạn có thể thấy vấn đề đệm thậm chí rõ ràng hơn bằng cách sử dụng một số ví dụ đánh số các dòng đầu ra để bạn có thể biết dòng nào bị thiếu:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

Một cách đơn giản để xem vấn đề đệm là sử dụng seqđể tạo danh sách các số. Chúng ta có thể dễ dàng biết được những con số bị mất:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

Giải pháp mẹo của tôi bằng cách sử dụng shell để đọc và lặp lại dòng đầu tiên hoạt động chính xác ngay cả với độ trễ giấc ngủ được thêm vào:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

Dưới đây là một ví dụ đầy đủ cho thấy các headvấn đề về bộ đệm, cho thấy mức độ headtiêu thụ toàn bộ bộ đệm của đầu ra chỉ để tạo ra năm dòng của nó mỗi lần. Bộ đệm tiêu thụ đó không có sẵn cho headlệnh tiếp theo trong chuỗi:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

Nhìn vào con số 1861ở trên, chúng ta có thể tính kích thước của bộ đệm đang được sử dụng bằng headcách đếm seqđầu ra từ 1đến 1860:

$ seq 1 1860 | wc -c
8193

Chúng tôi thấy rằng headbộ đệm bằng cách đọc toàn bộ 8KB (8 * 1024 byte) của đầu ra đường ống tại một thời điểm, thậm chí chỉ tạo ra một vài dòng đầu ra của chính nó.

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.