Làm thế nào để làm cho ống hai chiều giữa hai chương trình?


63

Mọi người đều biết cách tạo đường ống đơn hướng giữa hai chương trình (liên kết stdoutcủa chương trình thứ nhất và stdinthứ hai) : first | second.

Nhưng làm thế nào để làm cho ống hai chiều, tức là liên kết chéo stdinstdoutcủa hai chương trình? Có cách nào dễ dàng để làm điều đó trong một cái vỏ?

shell  pipe 

Câu trả lời:


30

Nếu các đường ống trên hệ thống của bạn là hai chiều (như trên Solaris 11 và một số BSD ít nhất, nhưng không phải là Linux):

cmd1 <&1 | cmd2 >&0

Coi chừng bế tắc mặc dù.

Cũng lưu ý rằng một số phiên bản của ksh93 trên một số hệ thống triển khai các đường ống ( |) bằng cặp ổ cắm . Các cặp ổ cắm là hai chiều, nhưng ksh93 rõ ràng tắt hướng ngược lại, vì vậy lệnh trên sẽ không hoạt động với các ksh93 đó ngay cả trên các hệ thống nơi các đường ống (như được tạo bởi lệnh pipe(2)gọi hệ thống) là hai chiều.


1
Có ai biết liệu điều này cũng hoạt động trên linux? (sử dụng archlinux tại đây)
heinrich5991


41

Chà, nó khá "dễ" với các ống có tên ( mkfifo). Tôi đặt dấu ngoặc kép dễ dàng vì trừ khi các chương trình được thiết kế cho việc này, có khả năng bế tắc.

mkfifo fifo0 fifo1
( prog1 > fifo0 < fifo1 ) &
( prog2 > fifo1 < fifo0 ) &
( exec 30<fifo0 31<fifo1 )      # write can't open until there is a reader
                                # and vice versa if we did it the other way

Bây giờ, thường có bộ đệm liên quan đến việc viết thiết bị xuất chuẩn. Vì vậy, ví dụ, nếu cả hai chương trình là:

#!/usr/bin/perl
use 5.010;
say 1;
print while (<>);

bạn mong đợi một vòng lặp vô hạn. Nhưng thay vào đó, cả hai sẽ bế tắc; bạn sẽ cần thêm $| = 1(hoặc tương đương) để tắt bộ đệm đầu ra. Sự bế tắc được gây ra bởi vì cả hai chương trình đang chờ đợi một cái gì đó trên stdin, nhưng họ không thấy nó vì nó nằm trong bộ đệm cứng của chương trình kia và chưa được ghi vào đường ống.

Cập nhật : kết hợp các đề xuất từ ​​Stéphane Charzelas và Joost:

mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1

không giống nhau, ngắn hơn và di động hơn.


23
Một ống có tên là đủ : prog1 < fifo | prog2 > fifo.
Andrey Vihrov

2
@AndreyVihrov đó là sự thật, bạn có thể thay thế một đường ống ẩn danh cho một trong những cái tên được đặt tên. Nhưng tôi thích sự đối xứng :-P
derobert

3
@ user14284: Trên Linux, bạn có thể làm điều đó với một cái gì đó như prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Andrey Vihrov

3
Nếu bạn thực hiện nó prog2 < fifo0 > fifo1, bạn có thể tránh điệu nhảy nhỏ của mình exec 30< ...(điều này chỉ hoạt động với bashhoặc yashcho các fds trên 10 như thế).
Stéphane Chazelas

1
@Joost Hmmm, có vẻ như bạn đúng rằng không cần, ít nhất là trong bash. Tôi có lẽ đã lo lắng rằng vì cái vỏ thực hiện các chuyển hướng (bao gồm cả việc mở các đường ống), nó có thể chặn đường sắt nhưng ít nhất là bash forks trước khi mở fifos. dashcó vẻ cũng ổn (nhưng cư xử hơi khác một chút)
derobert

13

Tôi không chắc đây có phải là điều bạn đang cố gắng làm không:

nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &

Điều này bắt đầu bằng cách mở một ổ cắm nghe trên cổng 8096 và sau khi kết nối được thiết lập, sinh ra chương trình secondvới stdinđầu ra stdoutlà luồng và là đầu vào luồng.

Sau đó, một giây ncđược khởi chạy kết nối với cổng nghe và sinh ra chương trình firstvới stdoutđầu vào là luồng đầu vào và stdinlà đầu ra luồng.

Điều này không chính xác được thực hiện bằng cách sử dụng một đường ống, nhưng nó dường như làm những gì bạn cần.

Vì điều này sử dụng mạng, điều này có thể được thực hiện trên 2 máy tính từ xa. Đây gần như là cách một máy chủ web ( second) và trình duyệt web ( first) hoạt động.


1
nc -Uđối với các socket miền UNIX chỉ lấy không gian địa chỉ tệp.
Ciro Santilli 新疆 心 心 事件

Tùy chọn -c không có sẵn trong Linux. Hạnh phúc của tôi là một cuộc sống ngắn ngủi :-(
pablochacin


6

bashphiên bản 4 có coproclệnh cho phép thực hiện điều này một cách thuần túy bashmà không cần đặt tên ống:

coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"

Một số vỏ khác cũng có thể làm coprocnhư vậy.

Dưới đây là câu trả lời chi tiết hơn nhưng xâu chuỗi ba lệnh, thay vì hai, điều này làm cho chỉ thú vị hơn một chút.

Nếu bạn hài lòng cũng sử dụng catstdbufsau đó xây dựng có thể được thực hiện dễ hiểu hơn.

Phiên bản sử dụng bashvới catstdbuf, dễ hiểu:

# start pipeline
coproc {
    cmd1 | cmd2 | cmd3
}
# create command to reconnect STDOUT `cmd3` to STDIN of `cmd1`
endcmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"
# eval the command.
eval "${endcmd}"

Lưu ý, phải sử dụng eval vì việc mở rộng biến trong <& $ var là bất hợp pháp trong phiên bản bash 4.2.25 của tôi.

Phiên bản sử dụng thuần túy bash: Chia thành hai phần, khởi chạy đường ống đầu tiên theo coproc, sau đó ăn trưa phần thứ hai, (một lệnh đơn hoặc đường ống) kết nối lại với đường dẫn thứ nhất:

coproc {
    cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"

Bằng chứng của khái niệm:

tập tin ./prog, chỉ là một prog giả để tiêu thụ, gắn thẻ và in lại các dòng. Sử dụng các lớp con để tránh các vấn đề về bộ đệm có thể là quá mức cần thiết, đây không phải là vấn đề ở đây.

#!/bin/bash
let c=0
sleep 2

[ "$1" == "1" ] && ( echo start )

while : ; do
  line=$( head -1 )
  echo "$1:${c} ${line}" 1>&2
  sleep 2
  ( echo "$1:${c} ${line}" )
  let c++
  [ $c -eq 3 ] && exit
done

nộp ./start_cat Đây là một phiên bản sử dụng bash, catstdbuf

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2 \
    | stdbuf -i0 -o0 ./prog 3
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"

hoặc tập tin ./start_part. Đây là một phiên bản chỉ sử dụng thuần túy bash. Đối với mục đích demo tôi vẫn đang sử dụng stdbufvì prog thực sự của bạn sẽ phải xử lý bộ đệm trong nội bộ để tránh bị chặn do bộ đệm.

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 ./prog 3 <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"

Đầu ra:

> ~/iolooptest$ ./start_part
starting first cmd
Delaying remainer
2:0 start
Running: exec stdbuf -i0 -o0 ./prog 3 <&63 >&60
3:0 2:0 start
1:0 3:0 2:0 start
2:1 1:0 3:0 2:0 start
3:1 2:1 1:0 3:0 2:0 start
1:1 3:1 2:1 1:0 3:0 2:0 start
2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
1:2 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start

Nó làm điều đó.


5

Một khối xây dựng thuận tiện để viết các ống hai chiều như vậy là thứ kết nối thiết bị xuất chuẩn và stdin của quy trình hiện tại với nhau. Hãy để chúng tôi gọi nó là ioloop. Sau khi gọi chức năng này, bạn chỉ cần bắt đầu một đường ống thông thường:

ioloop &&     # stdout -> stdin 
cmd1 | cmd2   # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin)

Nếu bạn không muốn sửa đổi các mô tả của trình bao cấp cao nhất, hãy chạy phần này trong một khung con:

( ioloop && cmd1 | cmd2 )

Đây là một triển khai di động của ioloop bằng cách sử dụng một đường ống có tên:

ioloop() {
    FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) &&
    trap "rm -f $FIFO" EXIT &&
    mkfifo $FIFO &&
    ( : <$FIFO & ) &&    # avoid deadlock on opening pipe
    exec >$FIFO <$FIFO
}

Ống có tên tồn tại trong hệ thống tập tin chỉ trong một thời gian ngắn trong quá trình thiết lập ioloop. Chức năng này không hoàn toàn POSIX vì mktemp không được dùng nữa (và có khả năng dễ bị tấn công cuộc đua).

Việc triển khai dành riêng cho linux bằng cách sử dụng / Proc / có thể không yêu cầu một đường dẫn có tên, nhưng tôi nghĩ rằng điều này là quá đủ tốt.


Chức năng thú vị, +1. Có lẽ có thể sử dụng một câu hoặc 2 thêm giải thích ( : <$FIFO & )chi tiết hơn. Cảm ơn đã đăng bài viết.
Alex Stragies

Tôi đã làm một chút nhìn xung quanh và đến trống rỗng; Tôi có thể tìm hiểu thông tin về sự phản đối ở mktempđâu? Tôi sử dụng nó một cách rộng rãi và nếu một công cụ mới hơn đã được sử dụng, tôi muốn bắt đầu sử dụng nó.
DopeGhoti

Alex: tòa nhà mở (2) trên một ống nan đang chặn. nếu bạn cố gắng "thực hiện <$ PIPE> $ PIPE", nó sẽ bị kẹt khi chờ quá trình khác mở phía bên kia. Lệnh ": <$ FIFO &" được thực thi trong một lớp con trong nền và cho phép chuyển hướng hai chiều hoàn thành thành công.
dùng873942

DopeGhoti: chức năng thư viện mktemp (3) C không được dùng nữa. Tiện ích mktemp (1) thì không.
dùng873942

4

Ngoài ra còn có

Vì @ StéphaneChazelas ghi chú chính xác trong các bình luận, các ví dụ trên là "mẫu cơ sở", anh ta có các ví dụ hay với các tùy chọn về câu trả lời của mình cho một câu hỏi tương tự .


Lưu ý rằng theo mặc định, socatsử dụng ổ cắm thay vì đường ống (bạn có thể thay đổi điều đó bằng commtype=pipes). Bạn có thể muốn thêm noforktùy chọn để tránh quá trình xã hội hóa thêm dữ liệu giữa các ống / ổ cắm. (cảm ơn vì đã chỉnh sửa câu trả lời của tôi btw)
Stéphane Chazelas

0

Có rất nhiều câu trả lời tuyệt vời ở đây. Vì vậy, tôi chỉ muốn thêm một cái gì đó để dễ dàng chơi xung quanh với họ. Tôi cho rằng stderrkhông được chuyển hướng ở bất cứ đâu. Tạo hai tập lệnh (giả sử a.sh và b.sh):

#!/bin/bash
echo "foo" # change to 'bar' in second file

for i in {1..10}; do
  read input
  echo ${input}
  echo ${i} ${0} got: ${input} >&2
done

Sau đó, khi bạn kết nối chúng theo bất kỳ cách tốt nào bạn sẽ thấy trên bảng điều khiển:

1 ./a.sh got: bar
1 ./b.sh got: foo
2 ./a.sh got: foo
2 ./b.sh got: bar
3 ./a.sh got: bar
3 ./b.sh got: foo
4 ./a.sh got: foo
4 ./b.sh got: bar
...
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.