Как сделать двунаправленный канал между двумя программами?

С недавним GNU tar на ударе или полученной оболочке:

XZ_OPT=-9 tar cJf tarfile.tar.xz directory

нижний регистр tar j переключатель использует bzip, верхний регистр J переключатель использует xz.

XZ_OPT переменная среды позволяет Вам установить xz опции, которые не могут быть переданы с помощью вызывающих приложений такой как tar.

Это теперь максимально.

Посмотрите man xz для других опций можно установить (-e/--extreme мог бы принести Вам некоторую дополнительную пользу сжатия для некоторых наборов данных).

XZ_OPT=-e9 tar cJf tarfile.tar.xz directory
64
02.11.2012, 16:40
8 ответов

Если каналы в Вашей системе двунаправлены (как они находятся на Солярисе 11 и некоторый BSDs, по крайней мере, но не Linux):

cmd1 <&1 | cmd2 >&0

Остерегайтесь мертвых блокировок все же.

Также обратите внимание, что некоторые версии ksh93 в некоторых системах реализуют каналы (|) использование пары сокета. пары сокета двунаправлены, но ksh93 явно закрывает обратное направление, таким образом, команда выше не работала бы с теми ksh93s даже в системах где каналы (как создано pipe(2) системный вызов), двунаправлены.

31
27.01.2020, 19:32

Ну, его довольно "легкое" с именованными каналами (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

делает то же, является короче, и более портативным.

42
27.01.2020, 19:32
  • 1
    , Один именованный канал достаточен: prog1 < fifo | prog2 > fifo. –  Andrey Vihrov 02.11.2012, 18:47
  • 2
    @AndreyVihrov это правда, можно заменить неименованным каналом один из именованных. Но мне нравится симметрия :-P –  derobert 02.11.2012, 19:26
  • 3
    @user14284: На Linux можно, вероятно, сделать это с чем-то как prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo. –  Andrey Vihrov 06.11.2012, 12:28
  • 4
    Если Вы делаете его prog2 < fifo0 > fifo1, можно избежать небольшого танца с exec 30< ... (который между прочим только работы с bash или yash для fds более чем 10 как этот). –  Stéphane Chazelas 05.04.2014, 23:08
  • 5
    @Joost Хм, появляется, Вы корректны, что нет никакой потребности, по крайней мере, в ударе. Я, вероятно, волновался, что, так как оболочка выполняет перенаправления (включая открытие каналов), это могло бы заблокировать — но по крайней мере колотить ветвления прежде, чем открыть fifos. dash кажется OK также (но ведет себя немного по-другому), –  derobert 09.11.2016, 19:27

Я не уверен, является ли это тем, что Вы пытаетесь сделать:

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

Это начинается путем открытия сокета слушания на порте 8096, и после того как соединение устанавливается, программа икры second с stdin как потоковый вывод и stdout как потоковый ввод.

Затем секунда nc запускается, который соединяется с портом прослушивания и порождает программу first с stdout как потоковый ввод и stdin поскольку поток производится.

Это точно не сделано с помощью канала, но это, кажется, делает то, в чем Вы нуждаетесь.

Поскольку это использует сеть, это может быть сделано на 2 удаленных компьютерах. Это - почти путь веб-сервер (second) и веб-браузер (first) работа.

13
27.01.2020, 19:32
  • 1
    И nc -U для сокетов домена UNIX, которые только берут адресное пространство файловой системы. –  Ciro Santilli 新疆改造中心法轮功六四事件 30.05.2017, 08:51
  • 2
    c не доступна в Linux. Мое счастье было недолгим одним :-( –  pablochacin 30.03.2018, 17:08
[1121215] Можно использовать [1121707] pipexec[1121708]: [12174]
10
27.01.2020, 19:32
[1122017]Здесь есть много отличных ответов. Так что я просто хочу добавить что-нибудь, чтобы с ними было легко играть. Я предполагаю, что [1122471]stderr[1122472] никуда не перенаправляется. Создайте два скрипта (скажем, a.sh и b.sh):

Затем, когда вы соединяете их любым хорошим способом, вы должны увидеть на консоли:

dd if=/dev/sda bs=512 count=63 | hexdump -C

0
27.01.2020, 19:32

Удобным блоком для написания таких двунаправленных труб является то, что соединяет stdout и stdin текущего процесса вместе. Назовем это 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/, которая не требует именованной трубы, но я думаю, что этой более чем достаточно.

5
27.01.2020, 19:32

Существует также

  • dpipe, "двунаправленная труба", включенная в пакет vde2, и включенная в текущие системы управления пакетами дистрибутива .

    dpipe processA = processB

  • socat, инструмент "все-все-все".

    socat EXEC:Program1 EXEC:Program2

Поскольку @StéphaneChazelas правильно отмечает в комментариях, вышеприведенные примеры являются "базовой формой", в его ответе на похожий вопрос есть хорошие примеры с опциями.

5
27.01.2020, 19:32

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, затем обедаем вторую часть (либо одна команда, либо конвейер), переподключая ее к первой:

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

file ./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}"

Output:

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

Вот и все.

7
27.01.2020, 19:32

Теги

Похожие вопросы