Подтвердить что ты не робот

Как сделать петлю трубы в bash

Предположим, что у меня есть программы 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. (Вам нужно получить еще не существующее имя файла, создать канал с таким именем, запустить "loop loop", очистить именованный канал.) Представьте, что вы больше не могли писать prog1 | prog2 и всегда должны были использовать именованные каналы для подключайте программы.

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

4b9b3361

Ответ 1

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

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

{ ... } >/dev/fd/0 - перенаправить stdout на stdin для последовательности канала в целом (т.е. перенаправляет вывод 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, поскольку кажется, что вы можете только определить, что цикл цикла завершен, попытавшись написать что-то писать ему.

Ответ 2

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

$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start

Ответ 3

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

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

Ответ 4

В моих решениях используется 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 [email protected]

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 [email protected]

Команда

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.

Ответ 5

Именованные каналы.

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

i.e fifo0, fifo1

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

processn < fifo (n-1) > fifon

Ответ 6

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

Ответ 7

Командный стек может быть составлен как строка из массива произвольных команд и оценивается с помощью 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"