Как сделать петлю трубы в 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
было бы здорово.
Ответы
Ответ 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"