Zrozumienie poleceń potokowych w systemach Unix / Linux

16

Mam dwa proste programy: Ai B. Auruchamia się jako pierwszy, a następnie Bpobiera „standardowe wyjście” Ai używa go jako „standardowe wejście”. Załóżmy, że używam systemu operacyjnego GNU / Linux, a najprostszym możliwym sposobem jest:

./A | ./B

Gdybym musiał opisać to polecenie, powiedziałbym, że jest to polecenie, które pobiera dane wejściowe (tj. Odczytuje) od producenta ( A) i zapisuje je konsumentowi ( B). Czy to poprawny opis? Czy coś mi brakuje?

nihulus
źródło
Powiązane: W jakiej kolejności są uruchamiane polecenia potokowe?
G-Man mówi „Przywróć Monikę”
To nie jest polecenie, to obiekt kenerl stworzony przez proces bash, który jest używany jako stdout procesu A i stdin jako B. Dwa procesy są uruchamiane prawie w tym samym czasie.
炸鱼 薯条 德里克
1
@ 炸鱼 Masz rację - ponieważ potok jądra jest obiektem w systemie plików pipefs, ale jeśli chodzi o samą powłokę - technicznie jest to polecenie potoku
Sergiy Kolodyazhnyy

Odpowiedzi:

26

Jedyną rzeczą w twoim pytaniu, która wyróżnia się jako błędna, jest to, że mówisz

Najpierw biegnie A, a następnie B dostaje stdout A

W rzeczywistości oba programy byłyby uruchamiane prawie w tym samym czasie. Jeśli nie ma danych wejściowych Bpodczas próby odczytu, będzie blokowany, dopóki nie będzie danych wejściowych do odczytu. Podobnie, jeśli nikt nie odczytuje danych wyjściowych A, zapisy będą blokowane, dopóki dane wyjściowe nie zostaną odczytane (część zostanie buforowana przez potok).

Jedyne, co synchronizuje procesy biorące udział w potoku, to operacje we / wy, tj. Odczyt i zapis w poprzek potoku. Jeśli nie nastąpi pisanie ani czytanie, oba procesy będą działać całkowicie niezależnie od siebie. Jeśli jeden zignoruje odczyt lub zapis drugiego, zignorowany proces zostanie zablokowany i ostatecznie zostanie zabity przez SIGPIPEsygnał (w przypadku zapisu) lub otrzyma warunek końca pliku w swoim standardowym strumieniu wejściowym (w przypadku odczytu) po zakończeniu drugiego procesu .

Idiomatyczny sposób opisania A | Bpolega na tym, że jest to potok zawierający dwa programy. Wyjście wytworzone na standardowym wyjściu z pierwszego programu jest dostępne do odczytu na standardowym wejściu przez drugi („[wyjście z] Ajest przesyłane do [wejście z]] B). Powłoka wykonuje wymagane czynności hydrauliczne, aby to umożliwić.

Jeśli chcesz użyć słów „konsument” i „producent”, to chyba też jest w porządku.

Fakt, że są to programy napisane w C, nie ma znaczenia. Fakt, że jest to Linux, macOS, OpenBSD lub AIX, nie ma znaczenia.

Kusalananda
źródło
2
Zapisywanie do pliku tymczasowego było używane w DOS-ie, ponieważ nie obsługiwał on wielu procesów.
CSM
2
@AlexVong Pamiętaj jednak, że twój przykład z plikiem tymczasowym nie jest dokładnie równoważny. Program może wyszukiwać zawartość pliku, ale dane wychodzące z potoku nie są widoczne. Lepszym przykładem byłoby użycie mkfifodo utworzenia nazwanego potoku, a następnie rozpoczęcie B w czytaniu w tle z potoku, a następnie zapisanie do niego A. Jest to jednak wybieranie nitów, ponieważ efekt byłby taki sam.
Kusalananda
2
@AlexVong Uproszczenia zawarte w tym artykule oddzielają go od prawdziwych rurociągów; równoległe wykonywanie jest naprawdę semantyczne, a nie optymalizacyjne. Jest to rozsądne, kłamliwe dla dzieci wyjaśnienie monadycznej oceny lub składu dla kogoś, kto widział rurociągi muszli, ale nie jest poprawne w innym kierunku. Wersja fifo Kusalanandy jest bliższa, ale części modelu propagacji błędów są naprawdę ważne i nie można ich powielać. (co mówię jako ktoś, kto jest bardzo w pociągu „rurociągi muszli to tylko składanie funkcji”)
Michael Homer
6
@AlexVong Nie, to zupełnie nie na miejscu. To nie jest w stanie wytłumaczyć nawet czegoś tak prostego jak yes | sed 10q
Wujek Billy,
1
@UncleBilly Zgadzam się z twoim przykładem. To pokazuje, że równoległe wykonanie jest naprawdę wymagane, również odnotowane przez Michała. W przeciwnym razie dostaniemy wypowiedzenie.
Alex Vong
2

Termin zwykle używany w dokumentacji to „potok”, który składa się z jednego lub więcej poleceń, patrz definicja POSIX. Technicznie rzecz biorąc, są to dwa polecenia, które masz, dwa podprocesy dla powłoki (albo fork()+exec()polecenia zewnętrzne lub podpowłoki)

Jeśli chodzi o część producent-konsument , rurociąg można opisać za pomocą tego wzoru, ponieważ:

  • Producent i konsument współużytkują bufor o stałej wielkości, a przynajmniej w systemach Linux i MacOS X istnieje stały rozmiar bufora potoku
  • Producent i konsument są luźno powiązani, polecenia w potoku nie znają się nawzajem (chyba że aktywnie sprawdzają /proc/<pid>/fdkatalog).
  • Producenci piszą do, stdouta konsumenci czytają stdintak, jakby wykonywali pojedyncze polecenie, czyli mogą istnieć bez siebie .

Różnica, którą widzę tutaj, polega na tym, że w przeciwieństwie do Producer-Consumer w innych językach, polecenia powłoki używają buforowania i zapisują standardowe wyjście po zapełnieniu bufora, ale nie ma wzmianki, że Producer-Consumer musi przestrzegać tej reguły - tylko czekać, gdy kolejka jest zapełniona lub odrzucić dane (czego innego nie robi potok).

Sergiy Kolodyazhnyy
źródło