Как сделать петлю из трубы в bash - PullRequest
21 голосов
/ 02 сентября 2008

Предположим, у меня есть программы P0, P1, ... P(n-1) для некоторых n > 0. Как я могу легко перенаправить вывод программы Pi в программу P(i+1 mod n) для всех i (0 <= i < n)?

Например, допустим, у меня есть программа square, которая многократно читает число и затем печатает квадрат этого числа, и программа calc, которая иногда печатает число, после которого она ожидает, что сможет прочитайте квадрат этого. Как мне соединить эти программы так, чтобы каждый раз, когда calc печатал число, square возводил в квадрат, он возвращал его calc?

Редактировать: Я, вероятно, должен уточнить, что я имею в виду под "легко". Решение с именованным pipe / fifo действительно работает (и я использовал его в прошлом), но для его правильной работы требуется совсем немного работы, если сравнивать его с использованием bash pipe. (Вам нужно получить еще не существующее имя файла, создать канал с таким именем, запустить «конвейерный цикл», очистить именованный канал.) Представьте, что вы больше не можете писать prog1 | prog2 и всегда должны использовать именованные каналы для подключать программы.

Я ищу что-то, что почти так же просто, как написать "нормальную" трубу. Например, что-то вроде { prog1 | prog2 } >&0 было бы здорово.

Ответы [ 7 ]

24 голосов
/ 04 сентября 2008

Потратив довольно много времени вчера, пытаясь перенаправить stdout на stdin, я остановился на следующем методе. Это не очень хорошо, но я думаю, что я предпочитаю это по названному решению pipe / fifo.

read | { P0 | ... | P(n-1); } >/dev/fd/0

{ ... } >/dev/fd/0 - перенаправить стандартный вывод на стандартный ввод для всей последовательности каналов (т. Е. Он перенаправляет вывод P (n-1) на вход P0). Использование >&0 или чего-то подобного не работает; Вероятно, это связано с тем, что bash предполагает, что 0 доступен только для чтения, в то время как он не против записи в /dev/fd/0.

Начальный read -pipe необходим, потому что без него дескриптор файла ввода и вывода является одним и тем же устройством pts (по крайней мере, в моей системе), и перенаправление не имеет никакого эффекта. (Устройство pts не работает как канал; запись в него выводит информацию на экран.) Делая ввод { ... } нормальным каналом, перенаправление дает желаемый эффект.

Для иллюстрации на моем примере calc / square:

function calc() {
  # calculate sum of squares of numbers 0,..,10

  sum=0
  for ((i=0; i<10; i++)); do
    echo $i                   # "request" the square of i

    read ii                   # read the square of i
    echo "got $ii" >&2          # debug message

    let sum=$sum+$ii
  done

  echo "sum $sum" >&2           # output result to stderr
}

function square() {
  # square numbers

  read j                         # receive first "request"
  while [ "$j" != "" ]; do
    let jj=$j*$j
    echo "square($j) = $jj" >&2  # debug message

    echo $jj                     # send square

    read j                       # receive next "request"
  done
}

read | { calc | square; } >/dev/fd/0

Запуск приведенного выше кода дает следующий вывод:

square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285

Конечно, этот метод довольно хакерский. В частности, деталь read имеет нежелательный побочный эффект: завершение «реальной» петли трубы не приводит к прекращению целостности. Я не мог придумать ничего лучше, чем read, так как кажется, что вы можете определить только, что конвейер завершился, попытавшись записать что-то в него.

15 голосов
/ 03 сентября 2008

Именованный канал может сделать это:

$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start
5 голосов
/ 02 сентября 2008

Это очень интересный вопрос. Я (смутно) помню задание, очень похожее в колледже 17 лет назад. Нам нужно было создать массив каналов, где наш код получал бы файловые дескрипторы для ввода / вывода каждого канала. Затем код будет разветвляться и закрывать неиспользуемые файловые дескрипторы.

Я думаю, вы могли бы сделать нечто подобное с именованными каналами в bash. Используйте mknod или mkfifo для создания набора каналов с уникальными именами, на которые вы можете сослаться, а затем разветвите свою программу.

3 голосов
/ 14 марта 2015

В моих решениях используется pipexec (большая часть реализации функции исходит из вашего ответа):

square.sh

function square() {
  # square numbers

  read j                         # receive first "request"
  while [ "$j" != "" ]; do
    let jj=$j*$j
    echo "square($j) = $jj" >&2  # debug message

    echo $jj                     # send square

    read j                       # receive next "request"
  done
}

square $@

calc.sh

function calc() {
  # calculate sum of squares of numbers 0,..,10

  sum=0
  for ((i=0; i<10; i++)); do
    echo $i                   # "request" the square of i

    read ii                   # read the square of i
    echo "got $ii" >&2          # debug message

    let sum=$sum+$ii
 done

 echo "sum $sum" >&2           # output result to stderr
}

calc $@

Команда

pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \
    "{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}"

Вывод (такой же, как в вашем ответе)

square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285

Комментарий: pipexec был разработан для запуска процессов и создания произвольных каналов между ними. Поскольку функции bash нельзя обрабатывать как процессы, необходимо иметь функции в отдельных файлах и использовать отдельный bash.

1 голос
/ 03 сентября 2008

Именованные трубы.

Создайте серию fifo, используя mkfifo

То есть fifo0, fifo1

Затем присоедините каждый процесс в терминах к нужным каналам:

processn fifon

0 голосов
/ 21 января 2009

Стек команд может быть составлен как строка из массива произвольных команд и оценивается с помощью eval. Следующий пример дает результат 65536.

function square ()
{
  read n
  echo $((n*n))
}    # ----------  end of function square  ----------

declare -a  commands=( 'echo 4' 'square' 'square' 'square' )

#-------------------------------------------------------------------------------
#   build the command stack using pipes
#-------------------------------------------------------------------------------
declare     stack=${commands[0]}

for (( COUNTER=1; COUNTER<${#commands[@]}; COUNTER++ )); do
  stack="${stack} | ${commands[${COUNTER}]}"
done

#-------------------------------------------------------------------------------
#   run the command stack
#-------------------------------------------------------------------------------
eval "$stack" 
0 голосов
/ 03 сентября 2008

Я сомневаюсь, что sh / bash может это сделать. ZSH был бы лучшим выбором, с его MULTIOS и функциями coproc.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...