Chuẩn bị dòng cuối cùng của stdin cho toàn bộ stdin


9

Hãy xem xét kịch bản này:

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

Điều này hoạt động và đầu ra:

line 3
line 1
line 2
line 3

Hãy nói rằng nguồn đầu vào của chúng tôi, thay vì là một tệp thực tế, thay vào đó là stdin:

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

Làm thế nào để chúng ta sửa đổi lệnh:

cat <(tail -1 "$tmpfile") "$tmpfile"

Vì vậy, nó vẫn tạo ra cùng một đầu ra, trong bối cảnh khác nhau này?

LƯU Ý: Heredoc cụ thể mà tôi đang trích dẫn, cũng như việc sử dụng chính Heredoc, chỉ mang tính minh họa. Bất kỳ câu trả lời chấp nhận được nên cho rằng nó đang nhận dữ liệu tùy ý thông qua stdin .


1
stdin luôn là một "tập tin thực tế" (fifo / socket / etc cũng là một tập tin; không phải tất cả các tập tin đều có thể tìm kiếm được). Câu trả lời cho câu hỏi của bạn là "sử dụng một tệp tạm thời" tầm thường hoặc một số điều kinh dị sẽ tải toàn bộ tệp trong bộ nhớ. "Làm cách nào tôi có thể truy xuất dữ liệu cũ từ một luồng mà không lưu trữ ở bất cứ đâu ?" không thể có một câu trả lời tốt
mosvy

1
@mosvy Đó là một câu trả lời hoàn toàn chấp nhận được nếu bạn muốn thêm nó.
Giô-na

2
@mosvy Như Jonah đã nói, câu trả lời nên được đăng trong hộp câu trả lời. Tôi biết rằng thật khó để đọc bất kỳ trang web nào vào lúc này, nhưng xin vui lòng bỏ qua màu đỏ đang dần trôi qua tầm nhìn của bạn và sử dụng văn bản thấp hơn.
wizzwizz4

Câu trả lời:


7

Thử:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

Thí dụ

Xác định một biến với đầu vào của chúng tôi:

$ input="line 1
> line 2
> line 3"

Chạy lệnh của chúng tôi:

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

Ngoài ra, tất nhiên, chúng ta có thể sử dụng tài liệu ở đây:

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

Làm thế nào nó hoạt động

  • x=x $0 ORS

    Điều này nối thêm từng dòng đầu vào cho biến x.

    Trong awk, ORSdấu phân tách đầu ra . Theo mặc định, nó là một nhân vật dòng mới.

  • END{printf "%s", $0 ORS x}

    Sau khi chúng tôi đã đọc trong toàn bộ tệp, phần này sẽ in dòng cuối cùng $0, theo sau là nội dung của toàn bộ tệp x.

Vì điều này đọc toàn bộ đầu vào vào bộ nhớ, nên nó sẽ không phù hợp với các đầu vào lớn ( ví dụ gigabyte).


Cảm ơn John. Vì vậy, không thể làm điều này theo cách tương tự với ví dụ tệp được đặt tên của tôi trong OP? Tôi đã tưởng tượng stdin bị trùng lặp bằng cách nào đó ... theo cách nào teeđó, nhưng với một stdin và một tập tin, chúng tôi sẽ chuyển cùng một stdin thành hai thay thế quy trình khác nhau. hoặc bất cứ điều gì sẽ tương đương với điều đó?
Giô-na

5

Nếu stdin trỏ đến một tệp có thể tìm kiếm (như trong trường hợp của bash (nhưng không phải tất cả các shell khác) ở đây, các tài liệu được triển khai với các tệp tạm thời), bạn có thể lấy đuôi và sau đó tìm kiếm lại trước khi đọc toàn bộ nội dung:

toán tử tìm kiếm có sẵn trong zshhoặc ksh93shell, hoặc các ngôn ngữ script như tcl / perl / python, nhưng không có trong bash. Nhưng bạn luôn có thể gọi những thông dịch viên nâng cao hơn bashnếu bạn phải sử dụng bash.

ksh93 -c 'tail -n1; cat <#((0))' <<...

Hoặc là

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

Bây giờ, điều đó sẽ không hoạt động khi stdin trỏ đến một tệp không thể tìm kiếm như ống hoặc ổ cắm. Sau đó, tùy chọn duy nhất là đọc và lưu trữ (trong bộ nhớ hoặc trong một tệp tạm thời ...) toàn bộ đầu vào.

Một số giải pháp lưu trữ trong bộ nhớ đã được đưa ra.

Với tempfile, với zsh, bạn có thể làm điều đó với:

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

Nếu trên Linux, có bashhoặc zshbất kỳ shell nào sử dụng tệp tạm thời cho tài liệu ở đây, bạn thực sự có thể sử dụng tệp tạm thời được tạo bởi tài liệu ở đây để lưu trữ đầu ra:

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF

4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

Vấn đề với việc dịch cái này sang cái gì đó sử dụng tailtailcần phải đọc toàn bộ tập tin để tìm ra kết thúc của nó. Để sử dụng nó trong đường ống của bạn, bạn cần phải

  1. Cung cấp đầy đủ nội dung của tài liệu cho tail.
  2. Cung cấp lại cho cat.
  3. Theo thứ tự đó.

Một mẹo nhỏ không phải là sao chép nội dung của tài liệu ( teethực hiện điều đó) mà là để đầu tailra xảy ra trước khi phần còn lại của tài liệu được xuất ra, mà không sử dụng tệp tạm thời trung gian.

Việc sử dụng sed(hoặc awk, như John1024 thực hiện ) sẽ loại bỏ việc phân tích cú pháp dữ liệu kép và vấn đề đặt hàng bằng cách lưu trữ dữ liệu trong bộ nhớ.

Các sedgiải pháp mà tôi đề xuất là để

  1. 1{h;d;}, lưu trữ dòng đầu tiên trong không gian giữ, nguyên trạng và bỏ qua dòng tiếp theo.
  2. H, nối các dòng khác vào không gian giữ bằng một dòng mới được nhúng.
  3. ${G;p;}, nối không gian giữ vào dòng cuối cùng với một dòng mới được nhúng và in dữ liệu kết quả.

Đây là bản dịch hoàn toàn theo nghĩa đen của giải pháp John1024 sed, với lời cảnh báo rằng tiêu chuẩn POSIX chỉ đảm bảo rằng không gian lưu trữ ở mức tối thiểu 8192 byte (8 KiB; nhưng nó khuyến nghị rằng bộ đệm này được phân bổ và mở rộng một cách linh hoạt khi cần, cả GNU sedvà BSD sedđang làm).


Nếu bạn cho phép mình sử dụng một đường ống có tên:

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

Điều này sử dụng teeđể gửi dữ liệu xuống mypipevà đồng thời đến cat. Trước cattiên, tiện ích sẽ đọc đầu ra từ tail(đọc từ mypipe, teeghi vào), sau đó nối thêm bản sao của tài liệu đến trực tiếp từ đó tee.

Có một lỗ hổng nghiêm trọng trong vấn đề này, ở chỗ, nếu tài liệu quá lớn (lớn hơn kích thước bộ đệm của đường ống), thì teechữ viết mypipecatsẽ bị chặn trong khi chờ ống (không tên) bị trống. Nó sẽ không được làm trống cho đến khi catđọc từ nó. catsẽ không đọc từ đó cho đến khi tailhoàn thành. Và tailsẽ không hoàn thành cho đến khi teehoàn thành. Đây là một tình huống bế tắc cổ điển.

Các biến thể

tee >( tail -n 1 >mypipe ) | cat mypipe -

có cùng một vấn đề.


2
Cái sedkhông hoạt động nếu đầu vào chỉ có một dòng (có thể sed '1h;1!H;$!d;G'). Cũng lưu ý rằng một số sedtriển khai có giới hạn thấp về kích thước của mẫu và không gian giữ của chúng.
Stéphane Chazelas

Các giải pháp đường ống được đặt tên là loại điều tôi đang tìm kiếm. Giới hạn là một sự xấu hổ. Tôi hiểu lời giải thích của bạn ngoại trừ phần đuôi và đuôi sẽ không kết thúc cho đến khi tee kết thúc, bạn có thể giải thích lý do tại sao lại như vậy không?
Giô-na

2

Có một công cụ có tên peetrong một tập hợp các tiện ích dòng lệnh thường được đóng gói với tên "moreutils mấy (hoặc có thể truy xuất được từ trang web chính của nó ).

Nếu bạn có thể có nó trên hệ thống của bạn thì tương đương với ví dụ của bạn sẽ như sau:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

Thứ tự các lệnh chạy qua peerất quan trọng vì chúng được thực thi theo trình tự được cung cấp.


1

Thử:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

Vì toàn bộ dữ liệu là dữ liệu theo nghĩa đen ("tài liệu ở đây là tài liệu") và sự khác biệt giữa dữ liệu đó và đầu ra mong muốn là không đáng kể, chỉ cần xoa bóp dữ liệu bằng chữ đó ngay tại đó để khớp với đầu ra.

Bây giờ giả sử line 3đến từ một nơi nào đó và được lưu trữ trong một biến gọi là lastline:

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

Trong tài liệu ở đây, chúng ta có thể tạo văn bản bằng cách thay thế các biến. Không chỉ vậy mà chúng ta có thể tính toán văn bản bằng cách sử dụng lệnh thay thế:

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

Chúng ta có thể nội suy nhiều dòng:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

Nói chung, tránh xử lý văn bản mẫu tài liệu ở đây; cố gắng tạo nó bằng cách sử dụng mã nội suy.


1
Thành thật tôi không thể biết đây có phải là một trò đùa hay không. Các cat <<EOS...trong OP đã được chỉ là một ví dụ Đứng cho "catting một tập tin tùy ý," để làm cho bài cụ thể và câu hỏi rõ ràng. Điều đó thực sự không rõ ràng đối với bạn, hay bạn chỉ nghĩ rằng sẽ thật thông minh khi diễn giải câu hỏi theo nghĩa đen?
Giô-na

@Jonah Câu hỏi nói rõ "[l] et 'nói rằng nguồn đầu vào của chúng tôi, thay vì là một tệp thực tế, thay vào đó là stdin:". Không có gì về "tập tin tùy ý"; đó là về tài liệu ở đây. Một tài liệu ở đây không phải là tùy ý. Nó không phải là một đầu vào cho chương trình của bạn, mà là một phần cú pháp của nó mà lập trình viên chọn.
Kaz

1
Tôi nghĩ bối cảnh và câu trả lời hiện có cho thấy rõ đó là trường hợp, nếu chỉ vì cách giải thích của bạn là chính xác, bạn phải cho rằng cả tôi và bất kỳ người đăng nào khác trả lời đều nhận ra rằng có thể sao chép và dán dòng mã. Tuy nhiên, tôi sẽ chỉnh sửa câu hỏi để làm cho nó rõ ràng.
Giô-na

1
Kaz, cảm ơn bạn đã trả lời, nhưng lưu ý ngay cả với chỉnh sửa của bạn, bạn đang thiếu ý định của câu hỏi. Bạn đang nhận được đầu vào multiline tùy ý thông qua một đường ống . Bạn không biết nó sẽ là gì. Nhiệm vụ của bạn là xuất ra dòng đầu vào cuối cùng, tiếp theo là toàn bộ đầu vào.
Giô-na

1
Kaz, đầu vào chỉ là một ví dụ. Hầu hết mọi người, bao gồm cả tôi, thấy hữu ích khi có một ví dụ về đầu vào thực và đầu ra dự kiến, thay vì chỉ là câu hỏi trừu tượng. Bạn là người duy nhất bị nhầm lẫn bởi điều này.
Giô-na

0

Nếu bạn không quan tâm đến thứ tự. Sau đó, điều này sẽ làm việc cat lines | tee >(tail -1). Như những người khác đã nói. Bạn cần đọc tệp hai lần hoặc đệm toàn bộ tệp để thực hiện theo thứ tự bạn yêu cầu.

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.