Як я можу реалізувати круговий потік даних між взаємопов'язаними командами?


19

Я знаю два типи, як команди можна з'єднати між собою:

  1. за допомогою Pipe (введення std-виводу на std-вхід наступної команди).
  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...]

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дзвінок там, щоб відключити буферизацію. Без цього? Тупики без жодного виводу.


дякую, я нічого не знав про буфери в цьому контексті, чи знаєте ви кілька ключових слів, щоб прочитати більше про це?
Абдул Аль Хазред

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вироблятиме одну ітерацію.


Милі зловживання make, але непотрібні: якщо ви використовуєте проміжний файл, чому б не просто використовувати цикл для управління ним?
alexis

@alexis, makefiles, ймовірно, надмірний. Мені не дуже зручно щодо циклів: я пропускаю поняття годинника, стан зупинки чи наочний приклад. Початкові діаграми запам’ятали мені «Workflow» та «підписи» функцій. Для складних діаграм ми в кінцевому підсумку потребуватимемо підключень даних або будемо вводити правила makefile. (це просто образлива інтуїція)
JJoao

@alexis, і, звичайно, я з вами згоден.
JJoao

Я не думаю, що це зловживання - makeйдеться про макроси, які є ідеальним додатком тут.
mikeserv

1
@mikeserv, Так. І всі ми знаємо, що зловживати інструментами - підпільна Magna Carta з 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тут - документи з трубами, а не temp-файлами, як це роблять інші два) .

Отже, при застосуванні до 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"

... яке якраз отримує останнє значення для bc's, aа не викликає a()функцію для її збільшення та друкує ...

.29566669586771958965

Він буде продовжувати працювати, насправді, поки я не вб'ю його і не зберу його IPC-труби ...

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT

1
Дуже цікаво. Зверніть увагу на останні bash та zsh, вам не потрібно вказувати дескриптор файлу, наприклад, eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)працює також
Thor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.