Uwaga: Odpowiedź odzwierciedla moje własne rozumienie tych mechanizmów , zebrane podczas badań i czytania odpowiedzi przez rówieśników na tej stronie i unix.stackexchange.com , i będzie aktualizowana w miarę upływu czasu. Nie wahaj się zadawać pytań lub sugerować ulepszenia w komentarzach. Sugeruję również, aby spróbować zobaczyć, jak syscalls działa w powłoce z strace
poleceniem. Proszę też nie dać się zastraszyć pojęciem wewnętrznym lub wywołaniami systemowymi - nie musisz ich znać ani być w stanie ich używać, aby zrozumieć, jak działa powłoka, ale zdecydowanie pomagają zrozumieć.
TL; DR
|
potoki nie są powiązane z wpisem na dysku, dlatego nie mają i- węzłowego systemu plików na dysku (ale mają i-węzeł w wirtualnym systemie plików pipefs w przestrzeni jądra), ale przekierowania często obejmują pliki, które mają wpisy na dysku, a zatem mają odpowiednie i-węzeł
- potoki nie są w
lseek()
stanie, więc polecenia nie mogą odczytać niektórych danych, a następnie przewinąć do tyłu, ale gdy przekierowujesz >
lub <
zwykle jest to plik, który jest lseek()
zdolny do obiektu, więc polecenia mogą poruszać się w dowolny sposób.
- przekierowania to manipulacje deskryptorami plików, których może być wiele; potoki mają tylko dwa deskryptory plików - jeden dla lewego polecenia i drugi dla prawego polecenia
- przekierowanie na standardowych strumieniach i potokach jest buforowane.
- rury prawie zawsze wymagają rozwidlenia, dlatego w grę wchodzą pary procesów; przekierowania - nie zawsze, ale w obu przypadkach wynikowe deskryptory plików są dziedziczone przez podprocesy.
- potoki zawsze łączą deskryptory plików (para), przekierowania - albo używają nazwy ścieżki, albo deskryptorów plików.
- potoki są metodą komunikacji między procesami, podczas gdy przekierowania są jedynie manipulacjami na otwartych plikach lub obiektach podobnych do plików
- oba wykorzystują
dup2()
wywołania systemowe pod maską, aby zapewnić kopie deskryptorów plików, w których występuje rzeczywisty przepływ danych.
- przekierowania można stosować „globalnie” za pomocą
exec
wbudowanego polecenia (zobacz to i to ), więc jeśli to zrobisz, exec > output.txt
każde polecenie będzie zapisywać output.txt
od tego momentu. |
potoki są stosowane tylko dla bieżącego polecenia (co oznacza albo polecenie proste, albo polecenie podobne do podpowłoki seq 5 | (head -n1; head -n2)
lub polecenie złożone.
Kiedy przekierowanie jest wykonywane na plikach, rzeczy takie jak echo "TEST" > file
i echo "TEST" >> file
oba używają open()
syscall na tym pliku ( patrz także ) i pobierają z niego deskryptor pliku, aby go przekazać dup2()
. |
Używaj tylko rur pipe()
i dup2()
syscall.
Jeśli chodzi o wykonywane komendy, potoki i przekierowania są niczym więcej niż deskryptorami plików - obiektami podobnymi do plików, do których mogą pisać na ślepo lub manipulować nimi wewnętrznie (co może powodować nieoczekiwane zachowania; apt
na przykład zwykle nawet nie zapisują na standardowe wyjście jeśli wie, że jest przekierowanie).
Wprowadzenie
Aby zrozumieć, czym różnią się te dwa mechanizmy, konieczne jest zrozumienie ich zasadniczych właściwości, historii tych dwóch mechanizmów oraz ich korzeni w języku programowania C. W rzeczywistości niezbędna jest znajomość deskryptorów plików oraz sposobu działania dup2()
i pipe()
wywołań systemowych lseek()
. Shell ma na celu uczynienie tych mechanizmów abstrakcyjnymi dla użytkownika, ale kopanie głębiej niż abstrakcja pomaga zrozumieć prawdziwą naturę zachowania powłoki.
Początki przekierowań i potoków
Zgodnie z artykułem Dennis Ritchie za prorocze Petroglyphs , rury pochodzi z 1964 r wewnętrznej notatce przez Malcolm Douglas McIlroy , w czasie gdy oni pracowali na systemie operacyjnym Multics . Zacytować:
Podsumowując, moje najmocniejsze obawy:
- Powinniśmy mieć kilka sposobów łączenia programów, takich jak wąż ogrodowy - wkręcamy inny segment, gdy staje się to konieczne, gdy trzeba masować dane w inny sposób. To także sposób IO.
Oczywiste jest, że w tym czasie programy były w stanie zapisywać na dysk, jednak było to nieefektywne, jeśli dane wyjściowe były duże. Cytując wyjaśnienie Briana Kernighana w wideo z Unix Pipeline :
Po pierwsze, nie musisz pisać jednego dużego programu - masz już mniejsze programy, które mogą już wykonywać część zadania ... Innym jest to, że ilość przetwarzanych danych nie zmieściłaby się, gdyby zapisałeś go w pliku ... ponieważ pamiętajmy, że wróciliśmy do czasów, w których dyski z tymi rzeczami miały, jeśli miałeś szczęście, megabajt lub dwa dane ... Więc rurociąg nigdy nie musiał tworzyć instancji całego wyniku .
Widoczna jest zatem różnica pojęciowa: potoki są mechanizmem umożliwiającym programom komunikowanie się ze sobą. Przekierowania - są sposobem zapisu do pliku na poziomie podstawowym. W obu przypadkach powłoka ułatwia te dwie rzeczy, ale pod maską dzieje się dużo.
Sięgając głębiej: wywołania systemowe i wewnętrzne działanie powłoki
Zaczynamy od pojęcia deskryptora pliku . Deskryptory plików opisują w zasadzie otwarty plik (czy to plik na dysku, w pamięci, czy plik anonimowy), który jest reprezentowany przez liczbę całkowitą. Dwa standardowe strumienie danych (stdin, stdout, stderr) to odpowiednio deskryptory plików 0,1 i 2. Skąd oni pochodzą ? Cóż, w poleceniach powłoki deskryptory plików są dziedziczone z ich rodzica - powłoki. Dotyczy to ogólnie wszystkich procesów - proces potomny dziedziczy deskryptory plików rodzica. W przypadku demonów powszechne jest zamykanie wszystkich odziedziczonych deskryptorów plików i / lub przekierowywanie do innych miejsc.
Powrót do przekierowania. Co to tak naprawdę jest Jest to mechanizm, który każe powłoce przygotować deskryptory plików dla polecenia (ponieważ przekierowania są wykonywane przez powłokę przed uruchomieniem polecenia) i wskazywać je tam, gdzie sugeruje to użytkownik. Standardowej rozdzielczości z przekierowaniem wyjścia jest
[n]>word
Czy [n]
istnieje numer deskryptora pliku. Kiedy to zrobisz, echo "Something" > /dev/null
sugeruje się, że liczba 1, i echo 2> /dev/null
.
Pod maską odbywa się to poprzez powielenie deskryptora pliku za pomocą dup2()
wywołania systemowego. Weźmy df > /dev/null
. Powłoka utworzy proces potomny, w którym zostanie df
uruchomiony, ale wcześniej otworzy się /dev/null
jako deskryptor pliku nr 3 i dup2(3,1)
zostanie wydana, co spowoduje utworzenie kopii deskryptora pliku 3, a kopia będzie 1. Wiesz, jak masz dwa pliki file1.txt
i file2.txt
, a kiedy to zrobisz cp file1.txt file2.txt
, będziesz mieć dwa takie same pliki, ale możesz nimi manipulować niezależnie? To tak samo dzieje się tutaj. Często widać, że przed uruchomieniem bash
zrobi dup(1,10)
deskryptor pliku kopii nr 1, który jest stdout
(i ta kopia będzie fd # 10) w celu przywrócenia go później. Ważne jest, aby pamiętać, że rozważając wbudowane polecenia(które są częścią samej powłoki i nie mają pliku w /bin
innym miejscu) ani prostych poleceń w nieinteraktywnej powłoce , powłoka nie tworzy procesu potomnego.
A potem mamy takie rzeczy jak [n]>&[m]
i [n]&<[m]
. Jest to powielanie deskryptorów plików, których ten sam mechanizm, co dup2()
tylko teraz, znajduje się w składni powłoki, wygodnie dostępnej dla użytkownika.
Jedną z ważnych rzeczy na temat przekierowania jest to, że ich kolejność nie jest ustalona, ale ma znaczenie dla tego, jak powłoka interpretuje to, czego chce użytkownik. Porównaj następujące:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
Praktyczne wykorzystanie ich w skryptach powłoki może być wszechstronne:
i wiele innych.
Krok po kroku z pipe()
idup2()
Jak powstają rury? Poprzez pipe()
syscall , który weźmie jako dane wejściowe tablicę (aka list) wywoływaną pipefd
z dwóch elementów typu int
(liczba całkowita). Te dwie liczby całkowite są deskryptorami plików. pipefd[0]
Będzie czytać koniec rury i pipefd[1]
będzie koniec zapisu. Więc w df | grep 'foo'
, grep
dostanie kopię pipefd[0]
i df
dostanie kopię pipefd[1]
. Ale jak ? Oczywiście z magią dup2()
syscall. W df
naszym przykładzie, powiedzmy, że pipefd[1]
ma numer 4, więc powłoka zrobi dziecko, zrób dup2(4,1)
(pamiętasz mój cp
przykład?), A następnie zrób to, execve()
by uruchomić df
. Naturalnie,df
odziedziczy deskryptor pliku # 1, ale nie będzie wiedział, że nie wskazuje już terminala, ale w rzeczywistości fd # 4, który jest właściwie końcem zapisu potoku. Oczywiście to samo nastąpi z grep 'foo'
wyjątkiem różnych liczb deskryptorów plików.
Ciekawe pytanie: czy moglibyśmy tworzyć rury, które przekierowują również fd # 2, a nie tylko fd # 1? Tak, w rzeczywistości to właśnie |&
robi bash. Standard POSIX wymaga języka poleceń powłoki do obsługi df 2>&1 | grep 'foo'
składni w tym celu, ale również to bash
robi |&
.
Należy zauważyć, że potoki zawsze zajmują się deskryptorami plików. Istnieje FIFO
lub nazwany potok , który ma nazwę pliku na dysku i pozwala użyć go jako pliku, ale zachowuje się jak potok. Ale |
typy potoków nazywane są potokami anonimowymi - nie mają nazw plików, ponieważ tak naprawdę są tylko dwoma obiektami połączonymi ze sobą. Fakt, że nie mamy do czynienia z plikami, ma również ważną implikację: potoki nie są w lseek()
stanie. Pliki w pamięci lub na dysku są statyczne - programy mogą używać lseek()
syscall do przeskakiwania do bajtu 120, następnie z powrotem do bajtu 10, a następnie do przodu do końca. Rury nie są statyczne - są sekwencyjne, dlatego nie można przewijać danych, które z nich otrzymujeszlseek()
. To właśnie powoduje, że niektóre programy są świadome, czy czytają z pliku lub z potoku, i w ten sposób mogą dokonać niezbędnych korekt w celu uzyskania wydajnej wydajności; innymi słowy, prog
mogę wykryć, czy zrobię cat file.txt | prog
lub prog < input.txt
. Przykładem takiej pracy jest ogon .
Dwie pozostałe bardzo interesujące właściwości potoków polegają na tym, że mają bufor, który w Linuksie ma 4096 bajtów , i faktycznie mają system plików zdefiniowany w kodzie źródłowym Linuksa ! Nie są po prostu obiektem do przekazywania danych, same są strukturą danych! W rzeczywistości, ponieważ istnieje system plików pipefs, który zarządza zarówno potokami, jak i FIFO, potoki mają numer i- węzła w swoim systemie plików:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
W systemie Linux potoki są jednokierunkowe, podobnie jak przekierowanie. W niektórych implementacjach uniksopodobnych - istnieją dwukierunkowe potoki. Chociaż dzięki magii skryptów powłoki można także tworzyć potoki dwukierunkowe w systemie Linux .
Zobacz też:
thing1 > temp_file && thing2 < temp_file
że z rurami możesz zrobić więcej. Ale dlaczego nie użyć ponownie>
operatora, aby to zrobić, np.thing1 > thing2
Dla poleceńthing1
ithing2
? Dlaczego dodatkowy operator|
?