Czy> & - jest bardziej wydajny niż> / dev / null?

58

Wczoraj przeczytałem ten komentarz SO, który mówi, że w powłoce (przynajmniej bash) >&-„ma taki sam wynik jak” >/dev/null.

Ten komentarz faktycznie odnosi się do przewodnika ABS jako źródła jego informacji. Ale to źródło mówi, że >&-składnia „zamyka deskryptory plików”.

Nie jest dla mnie jasne, czy dwie akcje zamknięcia deskryptora pliku i przekierowania go do urządzenia zerowego są całkowicie równoważne. Moje pytanie brzmi: czy są?

Na pierwszy rzut oka wydaje się, że zamknięcie deskryptora jest jak zamknięcie drzwi, ale przekierowanie do zerowego urządzenia otwiera drzwi do otchłani! Oba nie wydają mi się dokładnie takie same, ponieważ jeśli zobaczę zamknięte drzwi, nie spróbuję z nich niczego wyrzucić, ale jeśli zobaczę otwarte drzwi, założę się, że mogę.

Innymi słowy, zawsze zastanawiałem się, czy >/dev/nullśrodki, które cat mybigfile >/dev/nullfaktycznie przetwarzają każdy bajt pliku i zapisać go na /dev/nullktórym zapomina. Z drugiej strony, jeśli powłoka napotka zamknięty deskryptor pliku, myślę (ale nie jestem pewien), że po prostu nic nie napisze, choć pozostaje pytanie, czy catnadal będzie czytał każdy bajt.

Ten komentarz mówi >&-i >/dev/nullpowinien ” być taki sam, ale nie jest to dla mnie tak głośna odpowiedź. Chciałbym uzyskać bardziej autorytatywną odpowiedź z pewnym odniesieniem do standardowego lub źródłowego rdzenia, czy nie ...

jamadagni
źródło
Gdybym chciał być charytatywny, powiedziałbym, że komentarz nie oznaczał, że zostały zaimplementowane w ten sam sposób, tylko że oba mają taki sam efekt końcowy, uniemożliwiając ci zobaczenie wyników programu.
Barmar

Odpowiedzi:

71

Nie, z pewnością nie chcesz zamykać deskryptorów plików 0, 1 i 2.

Jeśli to zrobisz, aplikacja po raz pierwszy otworzy plik, zmieni się na stdin / stdout / stderr ...

Na przykład, jeśli wykonasz:

echo text | tee file >&-

Kiedy tee(przynajmniej niektóre implementacje, takie jak busybox) otworzy plik do zapisu, zostanie on otwarty w deskryptorze pliku 1 (standardowe wyjście). Więc teenapiszę textdwa razy w file:

$ echo text | strace tee file >&-
[...]
open("file", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 1
read(0, "text\n", 8193)                 = 5
write(1, "text\n", 5)                   = 5
write(1, "text\n", 5)                   = 5
read(0, "", 8193)                       = 0
exit_group(0)                           = ?

Wiadomo, że powoduje to luki w zabezpieczeniach. Na przykład:

chsh 2>&-

I chsh(aplikacja setuid) może kończyć się pisaniem komunikatów o błędach w /etc/passwd.

Niektóre narzędzia, a nawet niektóre biblioteki starają się temu zapobiec. Na przykład GNU teeprzeniesie deskryptor pliku na jeden powyżej 2, jeśli pliki, które otwiera do zapisu, mają przypisane 0, 1, 2, a zajęty teenie.

Większość narzędzi, jeśli nie mogą pisać na standardowe wyjście (ponieważ na przykład nie jest otwarte), zgłosi komunikat o błędzie na stderr (w języku użytkownika, co oznacza dodatkowe przetwarzanie do otwierania i analizowania plików lokalizacyjnych ...), więc będzie znacznie mniej wydajny i może spowodować awarię programu.

W każdym razie nie będzie bardziej wydajne. Program nadal wykona write()wywołanie systemowe. Może być bardziej efektywny tylko wtedy, gdy program zrezygnuje z zapisu na stdout / stderr po pierwszym nieudanym write()wywołaniu systemowym, ale programy na ogół tego nie robią. Zazwyczaj wychodzą z błędem lub próbują dalej.

Stéphane Chazelas
źródło
4
Myślę, że ta odpowiedź byłaby jeszcze lepsza, gdyby ostatni akapit był na górze (ponieważ to właśnie najbardziej odpowiada odpowiedź na pytanie PO), a następnie kontynuował dyskusję, dlaczego to zły pomysł, nawet jeśli w większości działał. Ale wezmę to, mam głos. ;)
CVn
@ StéphaneChazelas: Jak powiedział Michael, spodziewałem się ostatniego paragrafu na górze, ale dziękuję za wyjaśnienie, że prawdopodobnie tylko stwarza problemy. Sądzę więc, że dodatkowym pytaniem byłoby, kiedy zamknięcie FD rzeczywiście przyda się? Czy powinienem zadać to jako osobne pytanie?
jamadagni
@jamadagni, zobacz unix.stackexchange.com/search?q=user%3A22565+%223%3E%26-%22 dla niektórych przykładów
Stéphane Chazelas
1
@jamadagni Jeśli link podany przez Stéphane'a nie odpowiada na pytanie, powiedziałbym, że brzmi to jak początek osobnego pytania, ponieważ nie jest to bezpośrednio związane ze względną wydajnością tych dwóch metod.
CVn
1
Rozumiem, że Stephane zaczyna od tego ważnego ostrzeżenia bezpieczeństwa, ponieważ byłoby mniej widoczne, gdyby ostatni akapit był na górze. +1 ode mnie
Olivier Dulac
14

IOW Zawsze zastanawiałem się, czy >/dev/nulloznacza to, cat mybigfile >/dev/nullże faktycznie przetworzyłby każdy bajt pliku i zapisał go, aby /dev/nullgo zapomnieć.

To nie jest pełna odpowiedź na twoje pytanie, ale tak, powyższe to, jak to działa.

catodczytuje nazwane pliki lub standardowe wejście, jeśli nie ma nazw plików, i wysyła na standardowe wyjście zawartość tych plików, dopóki nie napotka EOF na (łącznie ze standardowym wejściem) na ostatnim nazwanym pliku. To jest jego praca.

Dodając >/dev/nullprzekierowujesz standardowe wyjście do / dev / null. Jest to specjalny plik (węzeł urządzenia), który wyrzuca wszystko, co w nim zapisane (i natychmiast zwraca EOF podczas odczytu). Zauważ, że przekierowanie We / Wy jest funkcją zapewnianą przez powłokę, a nie przez poszczególne aplikacje, i że w nazwie / dev / null nie ma nic magicznego , tylko to, co dzieje się tam w większości systemów uniksowych .

Należy również zauważyć, że specyficzna mechanika węzłów urządzeń różni się w zależności od systemu operacyjnego, ale cat (co w systemie GNU oznacza coreutils) jest wieloplatformowy (ten sam kod źródłowy musi działać przynajmniej w systemie Linux i Hurd) i dlatego nie może przyjmować zależności od konkretnych jąder systemu operacyjnego. Ponadto nadal działa, jeśli utworzysz alias / dev / null (w systemie Linux oznacza to węzeł urządzenia o tym samym głównym / mniejszym numerze urządzenia) o innej nazwie. I zawsze jest tak w przypadku pisania gdzie indziej, który zachowuje się tak samo (powiedzmy, / dev / zero).

Wynika z tego, że catnie jest świadomy specjalnych właściwości / dev / null i rzeczywiście prawdopodobnie nie jest świadomy przekierowania, ale nadal musi wykonać dokładnie taką samą pracę: odczytuje nazwane pliki i wysyła zawartość plik / te na standardowe wyjście. To, że standardowe wyjście catzdarza się popaść w pustkę, nie jest czymś catzwiązanym z tym.

CVn
źródło
2
Aby rozszerzyć swoją odpowiedź: Tak, cat mybigfile > /dev/nullspowoduje catodczytanie każdego bajtu bigfilepamięci. I dla każdego nbajtu zadzwoni write(1, buffer, n). Bez wiedzy o catprogramie writenie zrobi absolutnie nic (może z wyjątkiem trywialnej księgowości). Zapisywanie do /dev/nullnie wymaga przetwarzania każdego bajtu.
G-Man
2
Pamiętam, że byłem zdumiony, gdy przeczytałem źródło jądra Linux urządzenia / dev / null. Spodziewałem się, że będzie jakiś skomplikowany system darmowych () buforów itp., Ale nie, to po prostu zwrot ().
Brian Minton
4
@ G-Man: Nie wiem, czy możesz zagwarantować, że tak będzie we wszystkich przypadkach. Nie mogę teraz znaleźć dowody, ale pamiętam trochę realizację zarówno catlub cpże będzie działać przez mmaping duże kawałki pliku źródłowego do pamięci, a następnie wywołanie write()na wyznaczonej regionie. Jeśli piszesz do /dev/null, write()wywołanie natychmiast powróci bez błędów na stronach pliku źródłowego, więc nigdy nie zostanie odczytane z dysku.
Nate Eldredge
2
Ponadto coś takiego jak GNU catdziała na wielu platformach, ale przypadkowe spojrzenie na kod źródłowy pokaże wiele #ifdefs: to nie jest dosłownie ten sam kod, który działa na wszystkich platformach, i jest wiele sekcji zależnych od systemu.
Nate Eldredge
@NateEldredge: Interesujący punkt, ale właśnie opierałem się na odpowiedzi Michaela, więc nie zaprzeczasz mi tak bardzo, jak przeciwstawiasz się Michaelowi.
G-Man