Đây có phải là hành vi đuôi trong Lệnh nhóm được chỉ định bởi POSIX không?


7

Sử dụng tailkết hợp với các công cụ tiêu chuẩn khác trong Nhóm lệnh có thể tạo ra một số cấu trúc mạnh mẽ. Ví dụ: để có được dòng đầu tiên và cuối cùng của tệp:

$ seq 10 > file
$ { head -n1; tail -n1; } <file
1
10

Khi ăn nội dung tập tin từ một đường ống để lệnh nhóm, tailthất bại trong việc tạo ra, bởi vì một ống là un- lseek thể :

$ seq 10 | { head -n1; tail -n1; }
1

Bây giờ, khi nội dung đủ lớn, sẽ tailhoạt động:

$ seq 10000 | { head -n1; tail -n1; }
1
10000

Đó là bởi vì sau khi người đầu tiên lseekthất bại, tailbiết nó không phải là một lseekable mô tả tập tin và vì nội dung của các đường ống chưa được đọc tất cả bài viết nào, nó bắt đầu đọc nội dung cho đến khi kết thúc.

Theo quan điểm của người dùng, tôi hy vọng rằng hành vi đó phải nhất quán bất kể kích thước nội dung đầu vào. Tôi đã xem qua POSIX tail, lseektài liệu và không tìm thấy bất kỳ mô tả nào.

Là hành vi này được chỉ định bởi POSIX? Nếu không, làm thế nào tôi có thể làm cho kết quả luôn luôn nhất quán?


Tôi đã thử nghiệm với đuôi GNU và đuôi FreeBSD, cả hai đều có cùng một hành vi.


có thể đáng để chỉ ra rằng việc chia nhỏ đầu vào như thế có taillẽ không đặc biệt hữu ích và thực sự có thể giúp bạn làm việc nhiều hơn dưới mui xe. như stéphane đề cập, nó yêu cầu xác thực đầu vào bổ sung cho một tailcách đơn giản hơn là có thể tìm kiếm đến hết đầu vào vì nó phải so sánh bù đầu vào đó với đầu vào lseek()và kết quả không khác gì head -n1 file; tail -n1 file. tôi thấy những thứ đó hữu ích hơn khi cắt đầu vào ra khỏi đầu:while IFS= read -r v; do { printf %s\\n "$v"; head; } >&"$((1+(x=!x)))"; done <in >out1 2>out2
mikeerv

thực ra, tôi đoán khi bạn nhóm tailít nhất thì nó không nên in chồng lên nhau, nên dĩ nhiên là có. lời xin lỗi của tôi.
mikeerv

Câu trả lời:


7

Lưu ý rằng vấn đề không phải là có tailnhưng với headở đây mà đọc từ các đường ống hơn so với dòng đầu tiên nó có nghĩa là để đầu ra (vì vậy không có gì để lại cho tailđể đọc).

Và vâng, đó là POSIX phù hợp.

head được yêu cầu rời khỏi con trỏ trong stdin ngay sau dòng cuối cùng nó có đầu ra khi đầu vào có thể tìm kiếm được, nhưng không thì khác.

http://pub.opengroup.org/onlinepub/9699919799/utilities/V3_chap01.html :

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.

Để headcó thể làm điều đó đối với một tệp không thể tìm kiếm có nghĩa là nó sẽ phải đọc một byte tại một thời điểm sẽ không hiệu quả khủng khiếp¹. Đó là những gì readhoặc linetiện ích làm hoặc GNU sedvới -utùy chọn.

Vì vậy, bạn có thể thay thế head -n 20bằng gsed -u 20qnếu bạn muốn hành vi đó.

Mặc dù ở đây, bạn muốn:

sed -e 1b -e '$b' -e d

thay thế. Ở đây, chỉ có một lệnh gọi công cụ, do đó, không có vấn đề gì với bộ đệm bên trong không thể chia sẻ giữa hai lần gọi công cụ. Tuy nhiên, xin lưu ý rằng đối với các tệp lớn, sẽ kém hiệu quả hơn khi sedđọc toàn bộ tệp, trong khi đối với các tệp tailcó thể tìm kiếm sẽ bỏ qua phần lớn tệp bằng cách tìm kiếm ở gần cuối tệp.

Xem các cuộc thảo luận liên quan về bộ đệm tại Tại sao sử dụng vòng lặp shell để xử lý văn bản được coi là thực tiễn xấu? .

Lưu ý rằng tailphải xuất đuôi của luồng trên stdin. Trong khi, để tối ưu hóa và cho các tệp có thể tìm kiếm, việc triển khai có thể tìm đến cuối tệp để lấy dữ liệu ở đó, không được phép quay lại điểm trước khi vị trí ban đầu tailđược gọi ( Busybox tailđã từng có lỗi đó).

Vì vậy, ví dụ trong:

{ cat; tail -n 1; } < file

Mặc dù tailcó thể tìm kiếm trở lại dòng cuối cùng file, nhưng không. Stdin của nó là một luồng trống khi catđể lại con trỏ ở cuối tệp; không được phép lấy lại dữ liệu từ luồng đó bằng cách tìm kiếm ngược trong tệp.

(Văn bản ở trên bỏ qua việc làm rõ đang chờ xử lý của Nhóm mở và xem xét rằng nó không được thực hiện chính xác bởi một số triển khai)


Built Phần headdựng sẵn của ksh93(được bật nếu bạn đặt /opt/ast/bintrước $PATH), cho ổ cắm (một loại tệp không thể tìm kiếm) thay vì nhìn trộm vào đầu vào (sử dụng recvfrom(..., MSG_PEEK)) trước khi thực sự đọc nó để xem nó cần đọc bao nhiêu để đảm bảo rằng nó không cần đọc 'Đọc quá nhiều. Và quay lại đọc một byte mỗi lần cho các loại tệp khác. Đó là một chút hiệu quả hơn và tôi tin là lý do chính tại sao nó thực hiện các đường ống của nó với socketpair()s thay vì pipe(). Lưu ý rằng đó không phải là bằng chứng hoàn toàn ngu ngốc vì có một điều kiện chủng tộc có thể được kích hoạt nếu một quá trình khác đọc từ ổ cắm ở giữa nhìn trộmđọc .


Tôi muốn biết về tailhành vi. Sau lần thất bại đầu tiên lseek, nó bắt đầu đọc nội dung. Trong khi seq 10000 | tail -n1, nó thậm chí không cố gắng thực hiện bất kỳ lseek. POSIX đã xác định tailhành vi nếu lseekthất bại?
cuonglm

@cuonglm, nếu việc đọc câu trả lời của Stephane của tôi là chính xác, với seq 10 | { head -n1; tail -n1; }, tailkhông còn gì để đọc vì headđã tham lam làm lu mờ đầu vào. Trong mọi trường hợp, sẽ không có ý nghĩa tailkhi thử một lseektệp không thể tìm kiếm
iruvar

@cuonglm, nó chỉ cho thuê các tệp có thể tìm kiếm (mà nó có thể xác định bằng a fstat(0)). Xem thêm chỉnh sửa của tôi.
Stéphane Chazelas

@ StéphaneChazelas: Cảm ơn câu trả lời thấu đáo (như mọi khi :)). Điều kỳ diệu cuối cùng trong tâm trí tôi là khi lần lseekthử đầu tiên thất bại, POSIX có cho phép tailloại bỏ lseekthử và thay đổi hành vi của nó để đọc tệp cho đến khi kết thúc không?
cuonglm

Đây lseek()là một lựa chọn triển khai để tối ưu hóa, POSIX chỉ định hành vi, đó là lấy phần đuôi của đầu vào chứ không phải chi tiết về cách thực hiện. Trong mọi trường hợp, tôi không thấy GNU tailđang cố gắng thất bại lseekkhi đầu vào là một đường ống.
Stéphane Chazelas
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.