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

19

Я знаю два типа, как команды могут быть связаны друг с другом:

  1. используя Pipe (помещая std-output в std-input следующей команды).
  2. с помощью тройника (разделить выход на несколько выходов).

Я не знаю, возможно ли это все, поэтому я рисую гипотетический тип соединения:

введите описание изображения здесь

Как можно реализовать круговой поток данных между командами, как, например, в этом псевдокоде, где я использую переменные вместо команд.

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}
Абдул Аль Хазред
источник

Ответы:

16

Циклический цикл ввода / вывода реализован с tail -f

Это реализует круговой цикл ввода / вывода:

$ echo 1 >file
$ tail -f file | while read n; do echo $((n+1)); sleep 1; done | tee -a file
2
3
4
5
6
7
[..snip...]

Это реализует циклический цикл ввода / вывода, используя алгоритм синуса, который вы упомянули:

$ echo 1 >file
$ tail -f file | while read n; do echo "1+s(3*$n)" | bc -l; sleep 1; done | tee -a file
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
1.75155632167982146959
[..snip...]

Здесь, bcвыполняет математические s(...)операции с плавающей точкой и является нотацией bc для функции синуса.

Реализация того же алгоритма с использованием переменной

Для этого конкретного математического примера подход циклического ввода-вывода не требуется. Можно просто обновить переменную:

$ n=1; while true; do n=$(echo "1+s(3*$n)" | bc -l); echo $n; sleep 1; done
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
[..snip...]
John1024
источник
12

Для этого вы можете использовать FIFO, созданный с помощью mkfifo. Однако обратите внимание, что очень легко случайно создать тупик. Позвольте мне объяснить это - возьмите ваш гипотетический «круговой» пример. Вы передаете вывод команды на ее вход. Существует как минимум два способа, которыми это может зайти в тупик:

  1. Команда имеет выходной буфер. Это частично заполнено, но еще не было сброшено (фактически написано). Это будет сделано, как только он заполнится. Итак, он возвращается к чтению своих входных данных. Он будет находиться там вечно, потому что ожидаемый вход находится в буфере вывода. И он не будет сброшен, пока не получит этот вход ...

  2. Команда имеет кучу выходных данных для записи. Он начинает писать, но буфер канала ядра заполняется. Так что он сидит там, ожидая, пока они будут в буфере. Это произойдет, как только он прочитает свои входные данные, т. Е. Никогда не будет делать этого, пока не завершит запись чего-либо в свой вывод.

Тем не менее, вот как вы это делаете. Этот пример используется odдля создания бесконечной цепочки шестнадцатеричных дампов:

mkfifo fifo
( echo "we need enough to make it actually write a line out"; cat fifo ) \ 
    | stdbuf -i0 -o0 -- od -t x1 | tee fifo

Обратите внимание, что в конце концов останавливается. Почему? Это зашло в тупик, # 2 выше. Вы также можете заметить stdbufзвонок там, чтобы отключить буферизацию. Без этого? Дедлоки без какого-либо вывода.

derobert
источник
спасибо, я ничего не знал о буферах в этом контексте, знаете ли вы некоторые ключевые слова, чтобы узнать больше об этом?
Абдул Аль Хазред
1
@AbdulAlHazred Для буферизации ввода / вывода ищите буферизацию stdio . Для буфера ядра в канале конвейерный буфер работает.
Дероберт
4

В общем, я бы использовал Makefile (команда make) и попытался сопоставить вашу диаграмму с правилами makefile.

f1 f2 : f0
      command < f0 > f1 2>f2

Чтобы иметь повторяющиеся / циклические команды, нам нужно определить итерационную политику. С:

SHELL=/bin/bash

a.out : accumulator
    cat accumulator <(date) > a.out
    cp a.out accumulator

accumulator:
    touch accumulator     #initial value

каждый makeбудет производить одну итерацию за раз.

JJoao
источник
Симпатичное злоупотребление make, но ненужное: если вы используете промежуточный файл, почему бы просто не использовать цикл для управления им?
Алексис
@alexis, makefiles, вероятно, излишний. Меня не очень устраивают циклы: я скучаю по понятию часов, состоянию остановки или четкому примеру. Исходные диаграммы запомнились мне рабочими процессами и сигнатурами функций. Для сложных диаграмм нам понадобятся коннекты данных или правила, типизированные для make-файлов. (это просто оскорбительная интуиция)
JJoao
@alexis, и, конечно, я с тобой согласен.
JJoao
Я не думаю, что это злоупотребление - makeречь идет о макросах, которые здесь идеально подходят.
mikeserv
1
@mikeserv, да. И все мы знаем, что злоупотребление инструментами - это подпольная Великая хартия в Unix :)
JJoao
4

Знаете, я не уверен, что вам обязательно нужен повторяющийся цикл обратной связи, как изображают ваши диаграммы, настолько, насколько возможно вы могли бы использовать постоянный конвейер между сопроцессами . С другой стороны, это может быть не так уж много различий - когда вы открываете строку в сопроцессе, вы можете реализовать типичные циклы стилей, просто записывая информацию и читая информацию из нее, не делая ничего необычного.

Во-первых, может показаться, что bcдля вас это главный кандидат на сопроцесс. В bcвы можете defineфункции , которые могут сделать очень многое , что вы просите в вашем псевдокод. Например, некоторые очень простые функции для этого могут выглядеть так:

printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN

... который будет печатать ...

b=3
c=.14112000805986722210
a=1.14112000805986722210

Но, конечно, это не последний . Как только подоболочка, отвечающая за printfканал, завершает работу (сразу после printfзаписи a()\nв канал), канал разрушается, и bcввод закрывается, и он также завершает работу. Это не так полезно, как могло бы быть.

@derobert уже упомянул FIFO, что можно сделать, создав файл именованного канала с помощью mkfifoутилиты. По сути, это также просто каналы, за исключением того, что ядро ​​системы связывает запись файловой системы с обоих концов. Они очень полезны, но было бы лучше, если бы вы могли просто иметь канал, не рискуя получить его в файловой системе.

Как это бывает, ваша оболочка делает это много. Если вы используете оболочку, которая реализует подстановку процессов, то у вас есть очень простые способы получения прочного канала - такого типа, который вы можете назначить фоновому процессу, с которым вы можете общаться.

В bash, например, вы можете увидеть , как заместительная процесс работы:

bash -cx ': <(:)'
+ : /dev/fd/63

Вы видите, что это действительно замена . Оболочка заменяет значение во время расширения, которое соответствует пути к ссылке на канал . Вы можете воспользоваться этим - вам не нужно ограничивать использование этого канала только для связи с любым процессом, выполняемым внутри самой ()замены ...

bash -c '
    eval "exec 3<>"<(:) "4<>"<(:)
    cat  <&4 >&3  &
    echo hey cat >&4
    read hiback  <&3
    echo "$hiback" here'

... который печатает ...

hey cat here

Теперь я знаю, что разные оболочки выполняют процесс сопроцессора по-разному, и что существует специальный синтаксис bashдля его настройки (и, вероятно, zshтакже для), но я не знаю, как эти вещи работают. Я просто знаю , что вы можете использовать приведенные выше синтаксис делать практически то же самое , не все канитель в обоих bashи zsh- и вы можете сделать очень похожую вещь в dashи busybox ashдля достижения той же цели с здесь-документов (потому что dashи busyboxделать здесь- документы с конвейерами, а не временные файлы, как у двух других) .

Итак, применительно к bc...

eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"

... это сложная часть. И это самое интересное ...

set --
until [ "$#" -eq 10 ]
do    printf '%s()\n' b c a >&"$BCIN"
      set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"

... который печатает ...

b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965

... и он все еще работает ...

echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"

... который просто возвращает мне последнее значение для bcs aвместо вызова a()функции для его увеличения и печатает ...

.29566669586771958965

Фактически, он будет продолжать работать, пока я не убью его и не разорву его каналы IPC ...

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT
mikeserv
источник
1
Очень интересно. Обратите внимание, что с недавними bash и zsh вам не нужно указывать дескриптор файла, например, eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)работает
Thor