Jak mogę wdrożyć cykliczny przepływ danych między połączonymi poleceniami?


19

Znam dwa typy łączenia poleceń:

  1. za pomocą potoku (wstawianie wyjścia std do wejścia std następnego polecenia).
  2. za pomocą trójnika (podziel wyjście na wiele wyników).

Nie wiem, czy to wszystko, co jest możliwe, więc rysuję hipotetyczny typ połączenia:

wprowadź opis zdjęcia tutaj

Jak można wdrożyć cykliczny przepływ danych między poleceniami, na przykład w tym pseudokodzie, w którym zamiast poleceń używam zmiennych:

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}

Odpowiedzi:


16

Okrągła pętla we / wy zaimplementowana z tail -f

To implementuje okrągłą pętlę we / wy:

$ 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...]

To implementuje cykliczną pętlę wejścia / wyjścia przy użyciu wspomnianego algorytmu sinusoidalnego:

$ 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...]

Tutaj bcdokonuje się matematyki zmiennoprzecinkowej i s(...)jest notacją bc dla funkcji sinus.

Implementacja tego samego algorytmu zamiast zmiennej

W tym konkretnym przykładzie matematycznym cykliczne podejście we / wy nie jest potrzebne. Można po prostu zaktualizować zmienną:

$ 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

Możesz do tego użyć FIFO utworzonego za pomocą mkfifo. Zauważ jednak, że bardzo łatwo jest przypadkowo stworzyć impas. Pozwól, że wyjaśnię to - weź hipotetyczny „okrągły” przykład. Podajesz wynik polecenia do jego wejścia. Istnieją co najmniej dwa sposoby, aby zablokować:

  1. Polecenie ma bufor wyjściowy. Jest częściowo wypełniony, ale nie został jeszcze opróżniony (faktycznie napisany). Zrobi to, gdy się wypełni. Wraca więc do odczytu danych wejściowych. Będzie tam trwał wiecznie, ponieważ dane wejściowe, na które czeka, znajdują się w buforze wyjściowym. I nie zostanie wypłukany, dopóki nie otrzyma tego wejścia ...

  2. Polecenie ma wiele danych wyjściowych do napisania. Zaczyna pisać, ale bufor potoku jądra zapełnia się. Więc siedzi tam i czeka, aż znajdą się w buforze. Stanie się to, gdy tylko odczyta dane wejściowe, tj. Nigdy, ponieważ nie zrobi tego, dopóki nie skończy zapisywać żadnych danych wyjściowych.

To powiedziawszy, oto jak to zrobić. Ten przykład odilustruje tworzenie niekończącego się łańcucha zrzutów heksadecymalnych:

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

Zauważ, że ostatecznie się zatrzymuje. Dlaczego? Zakleszczenie, # 2 powyżej. Możesz również zauważyć tam stdbufpołączenie, aby wyłączyć buforowanie. Bez tego? Zakleszczenia bez wyjścia.


dzięki, nie wiedziałem nic o buforach w tym kontekście, czy znasz jakieś słowa kluczowe, aby przeczytać więcej na ten temat?
Abdul Al Hazred

1
@AbdulAlHazred Aby buforować dane wejściowe / wyjściowe, wyszukaj buforowanie standardowe . Wydaje się, że bufor bufora jądra w potoku działa.
derobert

4

Ogólnie korzystam z Makefile (polecenie make) i próbuję zmapować twój diagram do reguł makefile.

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

Aby mieć powtarzalne / cykliczne polecenia, musimy zdefiniować zasadę iteracji. Z:

SHELL=/bin/bash

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

accumulator:
    touch accumulator     #initial value

każdy makewytworzy jedną iterację naraz.


Słodkie nadużycie make, ale niepotrzebne: jeśli używasz pliku pośredniego, dlaczego po prostu nie używasz pętli do zarządzania nim?
Alexis

@alexis, makefiles to prawdopodobnie przesada. Nie czuję się zbyt dobrze z pętlami: brakuje mi pojęcia zegara, stanu zatrzymania lub wyraźnego przykładu. Początkowe diagramy pamiętały mnie Przepływy pracy i sygnatury funkcji. W przypadku skomplikowanych diagramów będziemy potrzebować połączeń danych lub reguł typu makefile. (to tylko obelżywa intuicja)
JJoao 30.03.15

@alexis i oczywiście się z tobą zgadzam.
JJoao

Nie sądzę, że jest to nadużycie - makedotyczy makr, które są tutaj doskonałą aplikacją.
mikeserv

1
@mikeserv, Yes. I wszyscy wiemy, że nadużyciami są podziemna Magna Carta z Uniksa :)
JJoao

4

Wiesz, nie jestem przekonany, że koniecznie potrzebujesz powtarzalnej pętli sprzężenia zwrotnego, gdy przedstawiają cię twoje diagramy, na tyle, na ile możesz użyć trwałego potoku między koprocesami . Z drugiej strony może się okazać, że nie ma zbyt dużej różnicy - po otwarciu linii w koprocesie można wdrożyć typowe pętle stylu, po prostu zapisując informacje i odczytując je, nie robiąc nic nadzwyczajnego.

Po pierwsze, wydaje się, że bcjest to dla ciebie główny kandydat na koproces. Wbc możesz definefunkcje, które mogą zrobić prawie to, o co prosisz w swoim pseudokodzie. Na przykład niektóre bardzo proste funkcje do wykonania tego mogą wyglądać:

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

... który wydrukowałby ...

b=3
c=.14112000805986722210
a=1.14112000805986722210

Ale oczywiście to nie trwa . Gdy tylko podpowłoka odpowiedzialna za printfpotok zostanie zamknięta (zaraz po printfzapisie a()\nna rurze), potok zostaje zerwany, a bcwejście zamyka się, a także zamyka. To nie jest tak przydatne, jak mogłoby być.

@derobert wspomniał już o FIFO jak można to zrobić, tworząc plik potoku nazwanego za pomocą mkfifonarzędzia. Są to w zasadzie tylko potoki, ale jądro systemu łączy wpis systemu plików z obydwoma końcami. Są to bardzo przydatne, ale byłoby miło, gdybyś mógł po prostu mieć fajkę bez ryzyka, że ​​zostanie ona podsłuchana w systemie plików.

Tak się składa, że ​​twoja skorupa często to robi. Jeśli używasz powłoki, która implementuje podstawianie procesów , masz bardzo prosty sposób na uzyskanie trwałego potoku - takiego, jaki możesz przypisać procesowi w tle, z którym możesz się komunikować.

W bash przykład możesz zobaczyć, jak działa podstawienie procesu:

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

Widzisz, to naprawdę jest zamiana . Podczas rozszerzania powłoka zastępuje wartość odpowiadającą ścieżce do łącza do potoku . Możesz z tego skorzystać - nie musisz ograniczać się do używania tego potoku tylko do komunikowania się z dowolnym procesem w obrębie() samego podstawienia ...

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

... które drukuje ...

hey cat here

Teraz wiem, że różne powłoki wykonują proces koprocesowania na różne sposoby - i że istnieje specyficzna składnia bashdo konfigurowania jednej (i prawdopodobnie także jednej zsh) - ale nie wiem, jak te rzeczy działają. Po prostu wiem, że można użyć powyższej składni zrobić praktycznie to samo, bez wszystkich żmudną procedurę zarówno bashi zsh- i można to zrobić bardzo podobną cechę dashi busybox ashaby osiągnąć ten sam cel z tu-dokumentów (bo dashibusybox zrobić tutaj- dokumenty zawierające potoki zamiast plików tymczasowych, jak robią to dwa pozostałe) .

Tak więc po zastosowaniu do 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="$!"

... to jest najtrudniejsza część. A to jest fajna część ...

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

... które drukuje ...

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

... i nadal działa ...

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

... który właśnie trafia mi ostatnią wartość dla bc„s azamiast wywołanie a()funkcji go i wydruki zwiększyć ...

.29566669586771958965

W rzeczywistości będzie działać, dopóki go nie zabiję i nie zniszczę rur IPC ...

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

1
Bardzo interesujące. Uwaga: w ostatnim bashu i zsh nie musisz określać deskryptora pliku, np. eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)Działa również
Thor
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.