đầu ăn thêm nhân vật


15

Lệnh shell sau đây dự kiến ​​chỉ in các dòng lẻ của luồng đầu vào:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

Nhưng thay vào đó, nó chỉ in dòng đầu tiên : aaa.

Điều tương tự không xảy ra khi nó được sử dụng với tùy chọn -c( --bytes):

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

Lệnh này xuất ra 1234512345như mong đợi. Nhưng điều này chỉ hoạt động trong việc triển khai coreutils của headtiện ích. Việc thực hiện busybox vẫn ăn thêm các ký tự, vì vậy đầu ra chỉ là 12345.

Tôi đoán cách thực hiện cụ thể này được thực hiện cho mục đích tối ưu hóa. Bạn không thể biết dòng kết thúc ở đâu, vì vậy bạn không biết bạn cần đọc bao nhiêu ký tự. Cách duy nhất để không tiêu thụ thêm ký tự từ luồng đầu vào là đọc byte luồng theo byte. Nhưng đọc từ luồng một byte mỗi lần có thể chậm. Vì vậy, tôi đoán headđọc luồng đầu vào vào một bộ đệm đủ lớn và sau đó đếm các dòng trong bộ đệm đó.

Điều tương tự không thể được nói cho trường hợp khi --bytestùy chọn được sử dụng. Trong trường hợp này, bạn biết bạn cần đọc bao nhiêu byte. Vì vậy, bạn có thể đọc chính xác số byte này và không nhiều hơn thế. Việc triển khai corelibs sử dụng cơ hội này, nhưng busybox thì không, nó vẫn đọc nhiều byte hơn mức cần thiết vào bộ đệm. Nó có thể được thực hiện để đơn giản hóa việc thực hiện.

Vì vậy, câu hỏi. Có đúng không khi headtiện ích tiêu thụ nhiều ký tự từ luồng đầu vào hơn so với yêu cầu? Có một số loại tiêu chuẩn cho các tiện ích Unix? Và nếu có, nó có chỉ định hành vi này không?

PS

Bạn phải nhấn Ctrl+Cđể dừng các lệnh trên. Các tiện ích Unix không thất bại khi đọc xa hơn EOF. Nếu bạn không muốn nhấn, bạn có thể sử dụng một lệnh phức tạp hơn:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

mà tôi đã không sử dụng cho đơn giản.


2
Cận cảnh unix.stackexchange.com/questions/48777/ trênunix.stackexchange.com/questions/84011/ trộm . Ngoài ra, nếu tiêu đề này đã có trên phim. Câu trả lời của tôi sẽ là Zardoz :)
dave_thedom_085

Câu trả lời:


30

Có đúng không khi tiện ích đầu tiêu thụ nhiều ký tự từ luồng đầu vào hơn so với yêu cầu?

Có, nó được cho phép (xem bên dưới).

Có một số loại tiêu chuẩn cho các tiện ích Unix?

Có, POSIX tập 3, Shell & Tiện ích .

Và nếu có, nó có chỉ định hành vi này không?

Nó làm, trong phần giới thiệu của nó:

Khi tiện ích tiêu chuẩn đọc tệp đầu vào có thể tìm kiếm và chấm dứt mà không có lỗi trước khi đến cuối tệp, tiện ích phải đảm bảo rằng tệp bù trong mô tả tệp mở được đặt đúng vị trí vừa qua byte cuối được xử lý bởi tiện ích. Đối với các tệp không thể tìm kiếm, trạng thái của tệp bù trong mô tả tệp mở cho tệp đó là không xác định.

headlà một trong những tiện ích tiêu chuẩn , do đó, việc triển khai tuân thủ POSIX phải thực hiện hành vi được mô tả ở trên.

GNU head không cố gắng để bộ mô tả tệp ở đúng vị trí, nhưng không thể tìm kiếm trên các đường ống, vì vậy trong thử nghiệm của bạn, nó không thể khôi phục vị trí. Bạn có thể thấy điều này bằng cách sử dụng strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

Trả readvề 17 byte (tất cả các đầu vào khả dụng), headxử lý bốn trong số đó và sau đó cố gắng di chuyển trở lại 13 byte, nhưng không thể. (Bạn cũng có thể thấy rằng GNU headsử dụng bộ đệm 8 KiB.)

Khi bạn yêu headcầu đếm byte (không chuẩn), nó sẽ biết có bao nhiêu byte để đọc, do đó, nó có thể (nếu được thực hiện theo cách đó) giới hạn việc đọc tương ứng. Đây là lý do tại sao head -c 5thử nghiệm của bạn hoạt động: GNU headchỉ đọc năm byte và do đó không cần tìm cách khôi phục vị trí của bộ mô tả tệp.

Nếu bạn viết tài liệu vào một tệp và thay vào đó, bạn sẽ có hành vi như sau:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc

2
Người ta có thể sử dụng các tiện ích line(hiện đã bị xóa khỏi POSIX / XPG nhưng vẫn có sẵn trên nhiều hệ thống) hoặc read( IFS= read -r line) thay vì đọc một byte mỗi lần để tránh sự cố.
Stéphane Chazelas

3
Lưu ý rằng việc head -c 5sẽ đọc 5 byte hay bộ đệm đầy đủ tùy thuộc vào việc triển khai (cũng lưu ý rằng đó head -ckhông phải là tiêu chuẩn), bạn không thể dựa vào đó. Bạn cần phải dd bs=1 count=5đảm bảo rằng sẽ không đọc quá 5 byte.
Stéphane Chazelas

Cảm ơn @ Stéphane, tôi đã cập nhật -c 5mô tả.
Stephen Kitt

Lưu ý rằng tích hợp headcủa việc ksh93đọc một byte tại một thời điểm head -n 1khi đầu vào không thể tìm kiếm được.
Stéphane Chazelas

1
@anton_rh, ddchỉ hoạt động chính xác với các đường ống bs=1nếu bạn sử dụng countđọc như trên các đường ống có thể trả về ít hơn yêu cầu (nhưng ít nhất một byte trừ khi đạt được eof). GNU ddiflag=fullblockthể làm giảm bớt điều đó mặc dù.
Stéphane Chazelas

6

từ POSIX

Các đầu tiện ích sẽ sao chép các tập tin đầu vào của nó vào đầu ra tiêu chuẩn, kết thúc đầu ra cho mỗi tập tin tại một điểm được chỉ định.

Nó không nói bất cứ điều gì về bao nhiêu head phải đọc từ đầu vào. Yêu cầu nó đọc từng byte một sẽ là ngớ ngẩn, vì nó sẽ rất chậm trong hầu hết các trường hợp.

Tuy nhiên, điều này được giải quyết trong readnội dung / tiện ích: tất cả các shell tôi có thể tìm thấy readtừ các ống một byte tại một thời điểm và văn bản tiêu chuẩn có thể được hiểu là điều này phải được thực hiện, để có thể chỉ đọc một dòng duy nhất:

Các đọc tiện ích sẽ đọc một dòng logic duy nhất từ đầu vào tiêu chuẩn vào một hoặc nhiều biến vỏ.

Trong trường hợp read, được sử dụng trong các kịch bản shell, trường hợp sử dụng phổ biến sẽ giống như thế này:

read someline
if something ; then 
    someprogram ...
fi

Ở đây, đầu vào tiêu chuẩn someprogramgiống như của vỏ, nhưng có thể dự kiến someprogramsẽ đọc mọi thứ xuất hiện sau dòng đầu vào đầu tiên được sử dụng bởi readvà không phải bất cứ thứ gì còn sót lại sau khi đọc bộ đệm read. Mặt khác, sử dụng headnhư trong ví dụ của bạn là không phổ biến hơn nhiều.


Nếu bạn thực sự muốn xóa mọi dòng khác, sẽ tốt hơn (và nhanh hơn) để sử dụng một số công cụ có thể xử lý toàn bộ đầu vào trong một lần, ví dụ:

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'

Nhưng hãy xem phần
Stephen Kitt

1
POSIX nói: "Khi một tiện ích tiêu chuẩn đọc tệp đầu vào có thể tìm kiếm và chấm dứt mà không có lỗi trước khi đến cuối tệp, tiện ích phải đảm bảo rằng phần bù tệp trong mô tả tệp mở được đặt đúng vị trí chỉ qua byte cuối cùng được xử lý bởi tiện ích. Đối với các tệp không thể tìm kiếm, trạng thái của tệp bù trong mô tả tệp mở cho tệp đó là không xác định. "
AlexP

2
Lưu ý rằng trừ khi bạn sử dụng -r, readcó thể đọc nhiều hơn một dòng (không có dòng IFS=này cũng sẽ loại bỏ các khoảng trắng và tab hàng đầu và dấu (với giá trị mặc định là $IFS)).
Stéphane Chazelas

@AlexP, vâng, Stephen chỉ liên kết phần đó.
ilkkachu

Lưu ý rằng tích hợp headcủa việc ksh93đọc một byte tại một thời điểm head -n 1khi đầu vào không thể tìm kiếm được.
Stéphane Chazelas

1
awk '{if (NR%2) == 1) print;}'

Hellóka :-) và chào mừng bạn đến với trang web! Lưu ý, chúng tôi thích các câu trả lời chi tiết hơn. Chúng nên hữu ích cho các nhân viên của tương lai.
peterh - Tái lập Monica
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.