Làm thế nào bạn có thể khác hai đường ống trong Bash?


143

Làm thế nào bạn có thể khác hai đường ống mà không sử dụng các tệp tạm thời trong Bash? Giả sử bạn có hai đường ống lệnh:

foo | bar
baz | quux

Và bạn muốn tìm difftrong đầu ra của họ. Một giải pháp rõ ràng là:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Có thể làm như vậy mà không cần sử dụng các tệp tạm thời trong Bash? Bạn có thể thoát khỏi một tệp tạm thời bằng cách đặt một trong các đường ống vào diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Nhưng bạn không thể đặt cả hai đường ống vào diff cùng một lúc (ít nhất là không theo cách rõ ràng nào). Có một số mẹo thông minh liên quan /dev/fdđến việc này mà không sử dụng các tập tin tạm thời?

Câu trả lời:


146

Một dòng có 2 tệp tmp (không phải những gì bạn muốn) sẽ là:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Với bash , bạn có thể thử mặc dù:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

Phiên bản thứ 2 sẽ nhắc rõ hơn cho bạn biết đầu vào nào, bằng cách hiển thị
-- /dev/stdinso với ++ /dev/fd/63hoặc một cái gì đó, thay vì hai fds được đánh số.


Thậm chí một ống có tên sẽ không xuất hiện trong hệ thống tệp, ít nhất là trên các hệ điều hành nơi bash có thể thực hiện thay thế quy trình bằng cách sử dụng tên tệp như /dev/fd/63để lấy tên tệp mà lệnh có thể mở và đọc từ để thực sự đọc từ bộ mô tả tệp đã mở mà bash đã đặt lên trước khi thực hiện lệnh. (tức là bash sử dụng pipe(2)trước ngã ba, và sau đó dup2để chuyển hướng từ đầu ra của quuxmột bộ mô tả tệp đầu vào cho diff, trên fd 63.)

Trên một hệ thống không có "phép thuật" /dev/fdhoặc /proc/self/fd, bash có thể sử dụng các đường dẫn có tên để thực hiện thay thế quy trình, nhưng ít nhất nó sẽ tự quản lý chúng, không giống như các tệp tạm thời và dữ liệu của bạn sẽ không được ghi vào hệ thống tệp.

Bạn có thể kiểm tra cách bash thực hiện quá trình thay thế bằng echo <(true)cách in tên tệp thay vì đọc từ nó. Nó in /dev/fd/63trên một hệ thống Linux điển hình. Hoặc để biết thêm chi tiết về chính xác những gì hệ thống gọi bash sử dụng, lệnh này trên hệ thống Linux sẽ theo dõi các cuộc gọi hệ thống mô tả tệp và tệp

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Nếu không có bash, bạn có thể tạo ra một đường ống có tên . Sử dụng -để nói diffđể đọc một đầu vào từ STDIN và sử dụng đường ống có tên như khác:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Lưu ý rằng bạn chỉ có thể chuyển một đầu ra thành nhiều đầu vào bằng lệnh tee:

ls *.txt | tee /dev/tty txtlist.txt 

Lệnh trên hiển thị đầu ra của ls * .txt đến thiết bị đầu cuối và xuất nó ra tệp văn bản txtlist.txt.

Nhưng với quá trình thay thế, bạn có thể sử dụng teeđể cung cấp cùng một dữ liệu vào nhiều đường ống:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar

5
ngay cả khi không có bash, bạn có thể sử dụng tạm thời fifomkfifo a; cmd >a& cmd2|diff a -; rm a
unhammer

Bạn có thể sử dụng một đường ống thông thường cho một trong các đối số : pipeline1 | diff -u - <(pipeline2). Sau đó, đầu ra sẽ nhắc rõ hơn cho bạn biết đầu vào nào, bằng cách hiển thị -- /dev/stdinso với ++ /dev/fd/67hoặc một cái gì đó, thay vì hai fds được đánh số.
Peter Cordes

quá trình thay thế ( foo <( pipe )) không sửa đổi hệ thống tập tin. Các đường ống là ẩn danh ; Nó không có tên trong hệ thống tập tin . Shell sử dụng lệnh pipegọi hệ thống để tạo ra nó, không phải mkfifo. Sử dụng strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'để theo dõi các cuộc gọi hệ thống tệp và mô tả tệp nếu bạn muốn tự mình xem. Trên Linux, /dev/fd/63là một phần của /prochệ thống tệp ảo; nó tự động có các mục cho mọi mô tả tập tin và nó không phải là bản sao của nội dung. Vì vậy, bạn không thể gọi đó là "tệp tạm thời" trừ khi foo 3<bar.txtđược tính
Peter Cordes

@PeterCordes Điểm tốt. Tôi đã bao gồm nhận xét của bạn trong câu trả lời để dễ nhìn hơn.
VonC

1
@PeterCordes Tôi sẽ để lại bất kỳ chỉnh sửa nào cho bạn: đó là điều khiến Stack Overflow trở nên thú vị: bất kỳ ai cũng có thể "sửa" một câu trả lời.
VonC

127

Trong bash, bạn có thể sử dụng các chuỗi con, để thực hiện các đường ống lệnh riêng lẻ, bằng cách đặt đường ống trong ngoặc đơn. Sau đó, bạn có thể đặt tiền tố này bằng <để tạo các đường dẫn có tên ẩn danh mà sau đó bạn có thể chuyển sang diff.

Ví dụ:

diff <(foo | bar) <(baz | quux)

Các ống có tên ẩn danh được quản lý bởi bash để chúng được tạo và hủy tự động (không giống như các tệp tạm thời).


1
Chi tiết hơn nhiều so với khả năng điều chỉnh của tôi trên cùng một giải pháp - lô ẩn danh -. +1
VonC

4
Điều này được gọi là quá trình thay thế trong Bash.
Franklin Yu

5

Một số người đến trang này có thể đang tìm kiếm một khác biệt theo từng dòng, thay vào đó commhoặc grep -fnên được sử dụng.

Một điều cần chỉ ra là, trong tất cả các ví dụ của câu trả lời, các khác biệt sẽ không thực sự bắt đầu cho đến khi cả hai luồng kết thúc. Kiểm tra điều này với ví dụ:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Nếu đây là một vấn đề, bạn có thể thử sd (stream diff), không yêu cầu sắp xếp (như commkhông) cũng như xử lý thay thế như các ví dụ trên, là đơn hàng hoặc cường độ nhanh hơn grep -f và hỗ trợ các luồng vô hạn.

Ví dụ thử nghiệm tôi đề xuất sẽ được viết như thế này trong sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Nhưng sự khác biệt là seq 100sẽ khác với seq 10ngay lập tức. Lưu ý rằng, nếu một trong các luồng là a tail -f, thì không thể thực hiện khác với quá trình thay thế.

Đây là một blogpost tôi đã viết về các luồng khác nhau trên thiết bị đầu cuối, giới thiệu sd.

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.