Jaka jest różnica między „Przekierowaniem” a „Rurą”?

204

To pytanie może brzmieć trochę głupio, ale tak naprawdę nie widzę różnicy między przekierowaniem a potokami.

Przekierowanie służy do przekierowania stdout / stdin / stderr, np ls > log.txt.

Rury służą do przekazywania danych wyjściowych polecenia jako danych wejściowych do innego polecenia, np ls | grep file.txt.

Ale dlaczego są dwaj operatorzy dla tej samej rzeczy?

Dlaczego po prostu nie napisać, ls > grepaby przekazać dane wyjściowe, czy nie jest to również rodzaj przekierowania? Czego mi brakuje?

John Threepwood
źródło

Odpowiedzi:

223

Rura służy do przekazywania danych wyjściowych do innego programu lub narzędzia .

Przekierowanie służy do przekazywania danych wyjściowych do pliku lub strumienia .

Przykład: thing1 > thing2vsthing1 | thing2

thing1 > thing2

  1. Twoja powłoka uruchomi program o nazwie thing1
  2. Wszystko, co thing1wyjdzie, zostanie umieszczone w pliku o nazwie thing2. (Uwaga - jeśli thing2istnieje, zostanie zastąpiony)

Jeśli chcesz przekazać dane wyjściowe z programu thing1do programu o nazwie thing2, możesz wykonać następujące czynności:

thing1 > temp_file && thing2 < temp_file

co by

  1. uruchom program o nazwie thing1
  2. zapisz wynik w pliku o nazwie temp_file
  3. uruchom program o nazwie thing2, udając, że osoba na klawiaturze wpisała zawartość temp_filejako dane wejściowe.

Jest to jednak niezgrabne, dlatego stworzyli rury jako prostszy sposób na zrobienie tego. thing1 | thing2robi to samo cothing1 > temp_file && thing2 < temp_file

EDYCJA, aby podać więcej szczegółów w pytaniu w komentarzu:

Próba >podania zarówno „przejścia do programu”, jak i „zapisu do pliku” może spowodować problemy w obu kierunkach.

Pierwszy przykład: próbujesz zapisać do pliku. Istnieje już plik o tej nazwie, który chcesz zastąpić. Jednak plik jest wykonywalny. Prawdopodobnie próbowałby uruchomić ten plik, przekazując dane wejściowe. Będziesz musiał zrobić coś takiego, jak zapisać dane wyjściowe pod nową nazwą pliku, a następnie zmienić nazwę pliku.

Drugi przykład: jak zauważył Florian Diesch, co jeśli w innym miejscu systemu istnieje inne polecenie o tej samej nazwie (to znaczy w ścieżce wykonania). Jeśli zamierzasz utworzyć plik o tej nazwie w bieżącym folderze, utkniesz.

Po trzecie: jeśli źle wpiszesz polecenie, nie ostrzeże Cię, że polecenie nie istnieje. W tej chwili, jeśli wpiszesz ls | gerp log.txt, powie ci bash: gerp: command not found. Jeśli >oznaczałoby to jedno i drugie, po prostu utworzyłby dla ciebie nowy plik (a następnie ostrzegł, że nie wie, co z tym zrobić log.txt).

David Oneill
źródło
Dziękuję Ci. Wspomniałeś, 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 > thing2Dla poleceń thing1i thing2? Dlaczego dodatkowy operator |?
John Threepwood,
1
„Weź dane wyjściowe i zapisz je do pliku” to inna akcja niż „Weź dane wyjściowe i przekaż je innemu programowi”. Przeredaguję więcej myśli w mojej odpowiedzi ...
David Oneill,
1
@JohnThreepwood Mają różne znaczenia. Co jeśli na przykład chcę przekierować coś do pliku o nazwie less? thing | lessi thing > lesssą zupełnie inni, ponieważ robią różne rzeczy. To, co zaproponujesz, wywołałoby dwuznaczność.
Darkhogg
Czy słuszne jest stwierdzenie, że „plik1> plik_czasowy” jest jedynie składniowym cukrem dla pliku „coś1 | tee plik_czasowy”? Odkąd dowiedziałem się o tee, prawie nigdy nie używam przekierowań.
Sridhar Sarnobat
2
@ Sridhar-Sarnobat nie, teepolecenie robi coś innego. teezapisuje dane wyjściowe zarówno na ekranie ( stdout), jak i pliku. Przekierowanie robi tylko plik.
David Oneill,
22

Jeśli znaczenie foo > barzależy od tego, czy istnieje polecenie o nazwie, barktóre spowodowałoby, że użycie przekierowania byłoby znacznie trudniejsze i bardziej podatne na błędy: za każdym razem, gdy chcę przekierować do pliku, najpierw musiałem sprawdzić, czy istnieje polecenie o nazwie jak mój plik docelowy.

Florian Diesch
źródło
Byłby to problem tylko wtedy, gdy piszesz do barkatalogu, który jest częścią $PATHzmiennej env. Jeśli jesteś w / bin /, to może być problem. Ale nawet wtedy barmusiałby mieć ustawione uprawnienia do plików wykonywalnych, aby powłoka nie tylko szukała pliku wykonywalnego, barale faktycznie mogła go wykonać. A jeśli problem dotyczy zastąpienia istniejącego pliku, nocloberopcja powłoki powinna zapobiec zastąpieniu istniejących plików w przekierowaniach.
Sergiy Kolodyazhnyy
13

Z Podręcznika administracji systemu Unix i Linux:

Przekierowanie

Powłoka interpretuje symbole <,> i >> jak instrukcje przekierowanie danej komendy za wejście lub wyjście do lub z pliku .

Rury

Aby połączyć STDOUT jednego polecenia z STDIN innego, użyj | symbol, powszechnie znany jako rura.

Tak więc moja interpretacja brzmi: jeśli jest to polecenie do polecenia, użyj potoku. Jeśli wysyłasz dane do lub z pliku, użyj przekierowania.

Pan cokolwiek
źródło
12

Istnieje zasadnicza różnica między dwoma operatorami:

  1. ls > log.txt -> To polecenie wysyła dane wyjściowe do pliku log.txt.

  2. ls | grep file.txt-> To polecenie wysyła dane wyjściowe polecenia ls do grep za pomocą potoku ( |), a polecenie grep wyszukuje plik.txt w danych wejściowych dostarczonych mu przez poprzednie polecenie.

Jeśli musiałbyś wykonać to samo zadanie przy użyciu pierwszego scenariusza, byłoby to:

ls > log.txt; grep 'file.txt' log.txt

Więc potok (with |) służy do wysyłania danych wyjściowych do innego polecenia, natomiast przekierowanie (z >) służy do przekierowania danych wyjściowych do jakiegoś pliku.

Ankit
źródło
3

Istnieje duża różnica składniowa między nimi:

  1. Przekierowanie jest argumentem dla programu
  2. Rura oddziela dwa polecenia

Można myśleć o przekierowań jak ten: cat [<infile] [>outfile]. Oznacza to, że kolejność nie ma znaczenia: cat <infile >outfilejest taka sama jak cat >outfile <infile. Możesz nawet łączyć przekierowania z innymi argumentami: cat >outfile <infile -bi cat <infile -b >outfileoba są całkowicie w porządku. Również można Ciąg razem więcej niż jedno wejście lub wyjście (wejścia będą czytane kolejno, a wszystkie wyjścia zostaną zapisane do każdego pliku wyjściowego) cat >outfile1 >outfile2 <infile1 <infile2. Celem lub źródłem przekierowania może być nazwa pliku lub nazwa strumienia (jak & 1, przynajmniej w bash).

Ale potoki całkowicie oddzielają jedno polecenie od drugiego, nie można ich mieszać z argumentami:

[command1] | [command2]

Potok bierze wszystko zapisane na standardowe wyjście z polecenia1 i wysyła je na standardowe wejście polecenia2.

Możesz także łączyć orurowanie i przekierowanie. Na przykład:

cat <infile >outfile | cat <infile2 >outfile2

Pierwszy catodczyta linie z infile, a następnie jednocześnie zapisze każdą linię do outfile i wyśle ​​ją do drugiej cat.

W drugim catstandardowe wejście najpierw czyta z potoku (zawartość infile), a następnie czyta infile2, zapisując każdą linię do outfile2. Po uruchomieniu tego plik wyjściowy będzie kopią pliku infil, a plik outfile2 będzie zawierał plik infile, a następnie plik infile2.

Na koniec robisz coś naprawdę podobnego do swojego przykładu, używając przekierowania „tutaj string” (tylko rodzina bash) i backticks:

grep blah <<<`ls`

da taki sam wynik jak

ls | grep blah

Ale myślę, że wersja przekierowująca najpierw odczyta wszystkie dane wyjściowe ls do bufora (w pamięci), a następnie prześle ten bufor do grepowania po jednej linii na raz, podczas gdy wersja potokowa pobierze każdą linię z ls, gdy się pojawi, i przekaż tę linię grepowi.

użytkownik319857
źródło
1
Nitpick: kolejność ma znaczenie przy przekierowaniu, jeśli przekierujesz jedną fd na drugą: echo yes 1>&2 2>/tmp/blah; wc -l /tmp/blah; echo yes 2>/tmp/blah 1>&2; wc -l /tmp/blahPonadto przekierowanie do pliku będzie korzystało tylko z ostatniego przekierowania. echo yes >/tmp/blah >/tmp/blah2napisze tylko do /tmp/blah2.
muru
2
Przekierowanie nie jest tak naprawdę argumentem programu. Program nie będzie wiedział ani nie dbał o to, gdzie idzie jego wyjście (lub wejście pochodzi). To tylko sposób na powiedzenie bashowi, jak uporządkować rzeczy przed uruchomieniem programu.
Alois Mahdal
3

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 stracepoleceniem. 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ą execwbudowanego polecenia (zobacz to i to ), więc jeśli to zrobisz, exec > output.txtkażde polecenie będzie zapisywać output.txtod 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" > filei echo "TEST" >> fileoba 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; aptna 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:

  1. 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/nullsugeruje 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 dfuruchomiony, ale wcześniej otworzy się /dev/nulljako 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.txti 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 bashzrobi 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 /bininnym 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ą pipefdz 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', grepdostanie kopię pipefd[0]i dfdostanie kopię pipefd[1]. Ale jak ? Oczywiście z magią dup2()syscall. W dfnaszym przykładzie, powiedzmy, że pipefd[1]ma numer 4, więc powłoka zrobi dziecko, zrób dup2(4,1)(pamiętasz mój cpprzykład?), A następnie zrób to, execve()by uruchomić df. Naturalnie,dfodziedziczy 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 bashrobi |&.

Należy zauważyć, że potoki zawsze zajmują się deskryptorami plików. Istnieje FIFOlub 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, progmogę wykryć, czy zrobię cat file.txt | proglub 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ż:

Sergiy Kolodyazhnyy
źródło
2

Aby dodać do innych odpowiedzi, istnieją również subtelne różnice semantyczne - np. Potoki zamykają się łatwiej niż przekierowania:

seq 5 | (head -n1; head -n1)                # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5   # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1)   # 1 and 2

W pierwszym przykładzie, gdy pierwsze wywołanie kończące się, headzamyka potok i seqkończy, więc nie ma danych wejściowych dla drugiego head.

W drugim przykładzie, head zużywa pierwszą linię, ale kiedy zamyka swój własny stdin potok , plik pozostaje otwarty do użycia przy następnym wywołaniu.

Trzeci przykład pokazuje, że jeśli użyjemy, readaby uniknąć zamknięcia rury, jest ona nadal dostępna w podprocesie.

Tak więc „strumień” jest tym, przez co przetaczamy dane (standardowe wyjście itd.), I jest taki sam w obu przypadkach, ale potok łączy strumienie z dwóch procesów, w których przekierowanie łączy strumienie między procesem a plikiem, więc widzę źródło zarówno podobieństw, jak i różnic.

PS Jeśli jesteś tak ciekawy i / lub zaskoczony tymi przykładami jak ja, możesz zagłębić się dalej, trapaby zobaczyć, jak procesy się rozwiązują, np .:

(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))

Czasami pierwszy proces zamyka się przed 1drukowaniem, a czasem później.

Ciekawe jest exec <&-również zamknięcie strumienia z przekierowania w celu przybliżenia zachowania potoku (choć z błędem):

seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`
Julian de Bhal
źródło
„kiedy kończy się pierwsze wezwanie do głowy, zamyka potok”. Jest to w rzeczywistości niedokładne z dwóch powodów. Jeden, (head -n1; head -n1) jest podpowłoką z dwoma poleceniami, z których każde dziedziczy odczyt końca potoku jako deskryptor 0, a zatem podpowłoka ORAZ każde polecenie ma otwarty deskryptor pliku. Drugi powód, możesz to zobaczyć za pomocą strace -f bash -c 'seq 5 | (głowa -n1; głowa -n1) ”. Więc pierwsza głowa zamyka tylko swoją kopię deskryptora pliku
Sergiy Kolodyazhnyy
Trzeci przykład jest również niedokładny, ponieważ readzużywa tylko pierwszą linię (to jeden bajt dla 1i nowa linia). seqwysłane łącznie 10 bajtów (5 cyfr i 5 nowych linii). Więc w buforze potoku pozostało 8 bajtów i dlatego drugi headdziała - dane nadal są dostępne w buforze potoku. Btw, głowa wychodzi tylko wtedy, gdy odczytano 0 bajtów, trochę jak whead /dev/null
Sergiy Kolodyazhnyy
Dziękuję za wyjaśnienie. Czy rozumiem poprawnie, że w seq 5 | (head -n1; head -n1)pierwszym wywołaniu opróżnia potok, więc nadal istnieje w stanie otwartym, ale bez danych dla drugiego wywołania head? Różnica w zachowaniu między potokiem a przekierowaniem polega na tym, że głowa wyciąga wszystkie dane z potoku, ale tylko 2 linie z uchwytu pliku?
Julian de Bhal,
To jest poprawne. I jest to coś, co widać po stracepoleceniu, które podałem w pierwszym komentarzu. Dzięki przekierowaniu plik tmp znajduje się na dysku, dzięki czemu jest widoczny (ponieważ używają lseek()syscall - polecenia mogą przeskakiwać po pliku od pierwszego bajtu do końca, jak chcą). Ale potoki są sekwencyjne i nie są widoczne. Więc jedyny sposób, aby głowa zrobiła to zadaniem jest przeczytanie wszystkiego na początku, lub jeśli plik jest duży - zamapuj część do pamięci RAM za pomocą mmap()połączenia. Kiedyś zrobiłem własny tailw Pythonie i napotkałem dokładnie ten sam problem
Sergiy Kolodyazhnyy
Ważne jest również, aby pamiętać, że najpierw czytany koniec potoku (deskryptor pliku) jest przekazywany do podpowłoki (...), a podpowłoka utworzy kopię swojego standardowego wejścia do każdego polecenia w środku (...). Więc są technicznie odczytywane z tego samego obiektu. Najpierw head myśli, że czyta ze swojego standardowego wejścia. Po drugie headuważa, że ​​ma swój własny standard. Ale w rzeczywistości ich fd # 1 (stdin) jest po prostu kopią tego samego fd, który jest czytany na końcu potoku. Również opublikowałem odpowiedź, więc może pomoże to wyjaśnić.
Sergiy Kolodyazhnyy
1

Mam problem z tym dzisiaj w C. Zasadniczo Pipe mają również inną semantykę niż przekierowania, nawet jeśli są wysyłane do stdin. Naprawdę myślę, że biorąc pod uwagę różnice, potoki powinny pójść gdzieś indziej niż stdin, aby pozwolić stdini nazwać to stdpipe(aby zrobić dowolną różnicę) można obsługiwać na różne sposoby.

Rozważ to. Podczas przesyłania danych z jednego programu do drugiego fstatwydaje się zwracać zero, st_sizemimo ls -lha /proc/{PID}/fdże pokazuje, że istnieje plik. Kiedy przekierowanie plik ten nie jest (przynajmniej na Debianie wheezy, stretcha jessiewanilii i ubuntu 14.04, 16.04wanilii.

Jeśli masz cat /proc/{PID}/fd/0przekierowanie, będziesz mógł powtarzać i czytać tyle razy, ile chcesz. Jeśli zrobisz to za pomocą potoku, zauważysz, że przy drugim uruchomieniu zadania kolejno nie uzyskasz tego samego wyniku.

MrMesees
źródło