Як зробити двонаправлену трубу між двома програмами?


63

Кожен знає , як зробити односпрямований трубу між двома програмами (пов'язують stdoutз першого і stdinвід другого): first | second.

Але як зробити двонаправлену трубу, тобто зшивання stdinта stdoutдвох програм? Чи є простий спосіб зробити це в оболонці?

shell  pipe 

Відповіді:


30

Якщо труби у вашій системі двосторонні (як це є у Solaris 11 та деяких BSD, принаймні, але не в Linux):

cmd1 <&1 | cmd2 >&0

Однак остерігайтеся тупиків.

Також зауважте, що деякі версії ksh93 в деяких системах реалізують труби ( |) за допомогою пари розеток . пари сокетів є двонаправленими, але ksh93 явно вимикає зворотний напрямок, тому команда, що наведена вище, не працюватиме з тими ksh93, навіть у системах, де труби (створені pipe(2)системним викликом) мають двонаправлений характер.


1
Хтось знає, чи працює це і на Linux? (тут використовується архлінукс)
heinrich5991


41

Ну, це досить "просто" з названими трубами ( mkfifo). Я легко розміщую лапки, тому що, якщо програми не призначені для цього, можливий глухий кут.

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

Зараз зазвичай буферизація бере участь у написанні stdout. Так, наприклад, якщо обидві програми були:

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

ви очікуєте нескінченного циклу. Але натомість обидва зайшли б у тупик; вам потрібно буде додати $| = 1(або еквівалент), щоб вимкнути вихідну буферизацію. Тупик виникає через те, що обидві програми чекають чогось на stdin, але вони його не бачать, оскільки його сидять у буфері stdout іншої програми, і досі не записані в трубу.

Оновлення : включення пропозицій від Stéphane Charzelas та Joost:

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

робить те саме, є коротшим і більш портативним.


23
Один іменований канал досить: prog1 < fifo | prog2 > fifo.
Андрій Віхров

2
@AndreyVihrov це правда, ви можете замінити анонімну трубку на одну з названих. Але мені подобається симетрія :-P
derobert

3
@ user14284: В Linux ви, ймовірно, можете це зробити з чимось на зразок prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Андрій Віхров

3
Якщо ви зробите це prog2 < fifo0 > fifo1, ви можете уникнути свого маленького танцю exec 30< ...(який, до речі, працює лише з bashабо yashдля FDS понад 10 подібних).
Стефан Шазелас

1
@Joost Hmmm, здається, ти маєш рацію, що немає потреби, принаймні в башті. Я, мабуть, хвилювався, що оскільки оболонка виконує переадресацію (включаючи відкривання труб), вона може заблокувати, але, принаймні, розтрощити виделки перед відкриттям фіфоса. dashздається ОК теж (але поводиться трохи інакше)
derobert

13

Я не впевнений, чи це ви намагаєтесь зробити:

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

Це починається з відкриття розетки прослуховування на порту 8096, і як тільки з'єднання встановлено, породжує програму secondз її stdinяк вихідного потоку, так і stdoutяк вхід потоку.

Потім ncзапускається секунда , яка підключається до порту прослуховування і нерегулярно програмує firstйого stdoutяк вхід потоку, так і його stdinяк вихідний потік.

Це точно не робиться за допомогою труби, але, здається, робити те, що потрібно.

Оскільки для цього використовується мережа, це можна зробити на двох віддалених комп'ютерах. Це майже спосіб роботи веб-сервера ( second) та веб-браузера ( first).


1
І nc -Uдля розеток домену UNIX, які займають лише адресний простір файлової системи.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

-c опція недоступна в Linux. Моє щастя було недовгим :-(
паблохацин


6

bashверсія 4 має coprocкоманду, яка дозволяє це робити в чистому режимі bashбез названих труб:

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

Також можуть зробити coprocце і інші снаряди .

Нижче більш детальна відповідь, але ланцюжок складається з трьох команд, а не двох, що робить лише трохи більш цікавим.

Якщо ви із задоволенням також використовуєте, catа stdbufпотім конструкцію можна зробити простішою для розуміння.

Версія використання bashз catі stdbuf, легко зрозуміти:

# 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}"

Зауважте, доведеться використовувати eval, тому що розширення змінної в <& $ var є незаконним у моїй версії bash 4.2.25.

Версія з використанням чистого bash: Розбийте на дві частини, запустіть перший конвеєр під копроком, потім другу частину обіду (або одну команду, або конвеєр), підключивши його до першої:

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

Доказ концепції:

файл ./prog, просто фіктивна прога для споживання, тегів та повторного друку рядків. Використання допоміжних оболонок для уникнення проблем із буферизацією, можливо, надмірного рівня, тут справа не в цьому.

#!/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

файл ./start_cat Це версія з використанням bash, catіstdbuf

#!/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}"

або файл ./start_part. Це версія, що використовує bashлише чистий . Для демонстраційних цілей я все ще використовую, stdbufтому що ваша реальна прога так чи інакше повинна мати справу з буферизацією, щоб уникнути блокування через буферизацію.

#!/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}"

Вихід:

> ~/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

Це робить це.


5

Зручний будівельний блок для написання таких двонаправлених труб - це те, що з'єднує між собою ступінь та струн поточного процесу. Назвемо це ioloop. Після виклику цієї функції потрібно лише запустити звичайну трубку:

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

Якщо ви не хочете змінювати дескриптори оболонки верхнього рівня, запустіть це в нижній частині:

( ioloop && cmd1 | cmd2 )

Ось портативна реалізація ioloop за допомогою названої труби:

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

Названа труба існує у файловій системі лише коротко під час налаштування ioloop. Ця функція не зовсім POSIX, оскільки mktemp застарілий (і потенційно вразливий для гоночної атаки).

Можлива специфічна для Linux програма, що використовує / proc /, яка не потребує названої труби, але я думаю, що ця більш ніж хороша.


Цікава функція, +1. Можливо, можна скористатись реченням або додано 2 пояснення ( : <$FIFO & )більш докладно. Дякую за публікацію
Алекс Страгіс

Я трохи озирнувся і підійшов порожнім; де я можу дізнатися інформацію про анулювання mktemp? Я його широко використовую, і якщо новий інструмент зайняв своє місце, я хотів би почати його використовувати.
DopeGhoti

Олексій: відкрита (2) систематична дзвінка на нанірованій трубі блокується. якщо ви спробуєте "виконати <$ PIPE> $ PIPE", він застрягне в очікуванні, коли інший процес відкриє іншу сторону. Команда ": <$ FIFO &" виконується в нижній частині корпусу у фоновому режимі і дозволяє успішно завершити двонаправлене перенаправлення.
user873942

DopeGhoti: функція бібліотеки mktemp (3) C застаріла. Утиліта mktemp (1) не є.
user873942

4

Є також

Як @ StéphaneChazelas в коментарях правильно зазначає, наведені вище приклади є "базовою формою", у нього є приємні приклади з варіантами відповіді на подібне питання .


Зауважте, що за замовчуванням socatзамість труб використовується розетки (ви можете змінити це за допомогою commtype=pipes). Ви можете додати noforkпараметр, щоб уникнути додаткового процесу socat, який просуває дані між трубами / розетками. (дякую за редагування на мою відповідь btw)
Стефан Шазелас

0

Тут є багато чудових відповідей. Тому я просто хочу додати щось, щоб легко пограти з ними. Я припускаю stderr, що ніде не переспрямовано. Створіть два сценарії (скажімо, a.sh та 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

Потім, коли ви підключите їх будь-яким добрим способом, ви повинні побачити на консолі:

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
...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.