Bezużyteczne wykorzystanie kota?

101

Jest to prawdopodobnie w wielu często zadawanych pytaniach - zamiast używać:

cat file | command

(co nazywa się bezużytecznym użyciem kota), poprawny sposób powinien być:

command < file

W drugim, „poprawnym” sposobie - system operacyjny nie musi generować dodatkowego procesu.
Pomimo tego, z 2 powodów nadal używałem bezużytecznego kota.

  1. bardziej estetyczny - lubię, gdy dane przesuwają się równomiernie tylko od lewej do prawej. I łatwiej wymienić catna coś innego ( gzcat, echo...), dodać 2nd plik lub wstawić nowy filtr ( pv, mbuffer, grep...).

  2. „Czułem”, że w niektórych przypadkach może być szybciej. Szybciej, ponieważ są 2 procesy, pierwszy ( cat) odczytuje, a drugi cokolwiek. Mogą działać równolegle, co czasami oznacza szybszą realizację.

Czy moja logika jest poprawna (z drugiego powodu)?

Leonid Volnitsky
źródło
22
catjest potokiem tożsamości . Przesyła tylko swoje dane wejściowe do swojego wyjścia. Jeśli drugi program w łańcuchu może pobierać dane wejściowe z tego samego argumentu, do którego przekazujesz cat(lub ze standardowego wejścia, jeśli nie przekażesz żadnego argumentu), to catjest absolutnie bezużyteczny i powoduje tylko rozwidlenie dodatkowego procesu i dodatkowy potok Utworzony.
Frédéric Hamidi
11
@ FrédéricHamidi, gdy cat nie ma argumentów lub jego argumentem jest -, jest to potok tożsamości. Kiedy ma więcej niż jeden argument nazwy pliku bez myślnika, staje się czymś więcej niż potokiem identyfikacyjnym i zaczyna służyć prawdziwemu celowi.
kojiro,
3
Wcześniej popularny link do partmaps.org jest niestety martwy. Treść jest teraz dostępna pod adresem porkmail.org/era/unix/award.html
tripleee
1
Zobacz też: unix.stackexchange.com/q/511827/20336
Mikko Rantalainen
2
Zauważam, że jeśli chcesz pokazać prawidłowy przepływ danych (powód 1), możesz to zrobić, umieszczając przekierowanie pliku przed poleceniem, tak jak w <file command1 | command2, chociaż nie byłoby zgody co do estetyki.
holdenweb

Odpowiedzi:

81

O nagrodzie nie wiedziałem, aż do dzisiaj, kiedy jakiś nowicjusz próbował przypisać mi UUOC za jedną z moich odpowiedzi. To był cat file.txt | grep foo | cut ... | cut .... Dałem mu trochę do zrozumienia i dopiero po tym, gdy to zrobiłem, odwiedziłem link, który mi podał, odnosząc się do pochodzenia nagrody i praktyki. Dalsze poszukiwania doprowadziły mnie do tego pytania. Nieco niestety, pomimo świadomego namysłu, żadna z odpowiedzi nie zawierała mojego uzasadnienia.

Nie chciałem być defensywny, odpowiadając mu. W końcu w moich młodszych latach napisałbym komendę tak, grep foo file.txt | cut ... | cut ...ponieważ za każdym razem, gdy robisz częste single grep, uczysz się umiejscowienia argumentu plik i jest już wiadomo, że pierwszy to wzorzec, a następne to nazwy plików.

Był to świadomy wybór, catgdy odpowiadałem na to pytanie, częściowo z powodu „dobrego smaku” (według słów Linusa Torvaldsa), ale głównie z nieodpartego powodu funkcji.

Ten ostatni powód jest ważniejszy, więc wypowiem go jako pierwszy. Kiedy oferuję rurociąg jako rozwiązanie, oczekuję, że będzie można go ponownie wykorzystać. Jest całkiem prawdopodobne, że rurociąg zostałby dodany na końcu lub połączony z innym rurociągiem. W takim przypadku posiadanie argumentu plikowego w celu grepa psuje możliwość ponownego użycia i całkiem możliwe, że zrobi to po cichu bez komunikatu o błędzie, jeśli argument plik istnieje. I. e. grep foo xyz | grep bar xyz | wcpoda, ile wierszy xyzzawiera, barpodczas gdy spodziewasz się liczby wierszy zawierających oba fooi bar. Konieczność zmiany argumentów na polecenie w potoku przed jego użyciem jest podatna na błędy. Dodaj do tego możliwość cichych niepowodzeń, a stanie się to szczególnie podstępną praktyką.

Ten pierwszy powód również nie jest nieważny, ponieważ wiele „ dobrego smaku ” jest po prostu intuicyjnym, podświadomym uzasadnieniem takich rzeczy, jak ciche niepowodzenia powyżej, o których nie możesz pomyśleć w momencie, gdy jakaś osoba potrzebująca edukacji mówi: „ale tak nie jest ten kot bezużyteczny ”.

Postaram się jednak również uświadomić poprzedni powód „dobrego smaku”, o którym wspomniałem. Powód ten ma związek z ortogonalnym duchem projektowania Uniksa. grepnie robi cuti lsnie robi grep. Dlatego przynajmniej grep foo file1 file2 file3jest sprzeczne z duchem projektowania. Jest to ortogonalne cat file1 file2 file3 | grep foo. To grep foo file1tylko szczególny przypadek grep foo file1 file2 file3, a jeśli nie traktujesz go tak samo, przynajmniej zużywasz cykle zegara mózgowego, próbując uniknąć bezużytecznej nagrody dla kota.

To prowadzi nas do argumentu, który grep foo file1 file2 file3jest konkatenacją i catkonkatenacją, więc jest to właściwe, cat file1 file2 file3ale ponieważ catnie jest konkatenowane, cat file1 | grep foodlatego naruszamy ducha cati wszechmocnego Uniksa. Cóż, gdyby tak było, Unix potrzebowałby innego polecenia, aby odczytać wyjście z jednego pliku i wypluć go na standardowe wyjście (nie paginować go ani cokolwiek po prostu spluwać na standardowe wyjście). Miałbyś więc sytuację, w której mówisz cat file1 file2lub mówisz, dog file1i sumiennie pamiętaj, aby uniknąć cat file1otrzymania nagrody, jednocześnie unikając, dog file1 file2ponieważ mam nadzieję, że projekt dogspowoduje błąd, jeśli określono wiele plików.

Miejmy nadzieję, że w tym momencie sympatyzujesz z projektantami Uniksa za to, że nie dołączali oddzielnego polecenia wypluwania pliku na standardowe wyjście, jednocześnie nadając mu nazwę catdo konkatenacji, zamiast nadawać mu inną nazwę. <edit>usunięto niepoprawne komentarze <w rzeczywistości <jest skutecznym narzędziem bez kopiowania do wypluwania pliku na standardowe wyjście, które można umieścić na początku potoku, więc projektanci Uniksa dodali coś specjalnie do tego celu</edit>

Kolejne pytanie brzmi: dlaczego ważne jest, aby polecenia, które po prostu wypluwają plik lub konkatenację kilku plików na standardowe wyjście, bez dalszego przetwarzania? Jednym z powodów jest unikanie posiadania każdego pojedynczego polecenia Uniksa, które działa na standardowym wejściu, aby wiedzieć, jak przeanalizować co najmniej jeden argument pliku wiersza poleceń i użyć go jako wejścia, jeśli istnieje. Drugim powodem jest uniknięcie konieczności pamiętania przez użytkowników: (a) gdzie znajdują się argumenty nazwy pliku; i (b) unikaj cichego błędu rurociągu, jak wspomniano powyżej.

To prowadzi nas do tego, dlaczego grepma dodatkową logikę. Uzasadnieniem jest umożliwienie użytkownikowi płynnego posługiwania się poleceniami, które są używane często i samodzielnie (a nie jako potok). Jest to niewielki kompromis ortogonalności w celu znacznego zwiększenia użyteczności. Nie wszystkie polecenia powinny być zaprojektowane w ten sposób, a polecenia, które nie są często używane, powinny całkowicie unikać dodatkowej logiki argumentów plikowych (pamiętaj, że dodatkowa logika prowadzi do niepotrzebnej kruchości (możliwość błędu)). Wyjątkiem jest dopuszczenie argumentów plikowych, takich jak w przypadku grep. (Nawiasem mówiąc, zwróć uwagę, że lsma zupełnie inny powód, aby nie tylko akceptować, ale prawie wymagać plików argumentów)

Wreszcie, co można było zrobić lepiej, to gdyby takie wyjątkowe polecenia, jak grep(ale niekoniecznie ls), generowały błąd, jeśli standardowe wejście jest również dostępne, gdy podano argumenty pliku.

nekromanta
źródło
53
Zauważ, że gdy grepjest wywoływany z wieloma nazwami plików, poprzedza znalezione wiersze nazwą pliku, w którym został znaleziony (chyba że wyłączysz to zachowanie). Może również raportować numery wierszy w poszczególnych plikach. Jeśli używasz tylko catdo podawania grep, tracisz nazwy plików, a numery wierszy są ciągłe we wszystkich plikach, a nie w każdym pliku. Dlatego istnieją powody, dla których trzeba grepobsługiwać wiele plików, catktórych nie można obsłużyć. Przypadki jednoplikowe i zerowe to po prostu szczególne przypadki ogólnego stosowania wielu plików grep.
Jonathan Leffler
38
Jak wskazano w odpowiedzi przez Kojiro , jest całkowicie możliwe i legalne uruchomienie rurociągu z < file command1 .... Chociaż konwencjonalne położenie operatorów przekierowania we / wy znajduje się po nazwie polecenia i jego argumentach, jest to tylko konwencja, a nie obowiązkowe umieszczenie. <Musi poprzedzać nazwę pliku. Tak, jest to blisko doskonałej symetrii pomiędzy >outputi <inputprzekierowań: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler
15
Myślę, że jednym z powodów, dla których ludzie rzucają kamieniem UUoC (w tym ja), jest przede wszystkim edukacja. Czasami ludzie przetwarzają gigabajty gigabajtów ogromnych plików tekstowych, w którym to przypadku minimalizacja potoków (UUoC, zwijanie sekwencyjnych grepsów w jeden, aso) jest kluczowa i często można bezpiecznie założyć, opierając się na pytaniu, że OP naprawdę po prostu nie wie, że małe poprawki mogą mieć ogromny wpływ na wydajność. W pełni zgadzam się z twoim zdaniem dotyczącym cykli mózgowych i dlatego regularnie używam kota, nawet gdy nie jest potrzebny. Ale ważne jest, aby wiedzieć, że nie jest to potrzebne.
Adrian Frühwirth
13
Proszę zrozumieć; W żadnym wypadku nie twierdzę, że catjest to bezużyteczne. Nie jest catto bezużyteczne; chodzi o to, że określony konstrukt nie wymaga użycia cat. Jeśli chcesz, pamiętaj, że jest to UUoC (bezużyteczne użycie cat), a nie UoUC (użycie bezużytecznego cat). Jest wiele sytuacji, w których catnależy użyć właściwego narzędzia; Nie mam problemu z używaniem go, gdy jest to właściwe narzędzie do użycia (i rzeczywiście, wspomnę o przypadku w mojej odpowiedzi).
Jonathan Leffler
6
@randomstring Słyszę cię, ale myślę, że to naprawdę zależy od przypadku użycia. Gdy jest używany w wierszu poleceń, jeden dodatkowy catw potoku może nie być wielkim problemem w zależności od danych, ale gdy jest używany jako środowisko programistyczne, może być absolutnie konieczne zaimplementowanie tych krytycznych dla wydajności rzeczy; zwłaszcza gdy mamy do czynienia z bashktórym, pod względem osiągów, jest jak koło w kształcie prostokąta (w porównaniu do kshzresztą. Mówię tu nawet 10x wolniej - bez żartów). Ci nie chcą zoptymalizować swoje widelce (i nie tylko tego) gdy ma do czynienia z większymi skryptów lub ogromnych pętli.
Adrian Frühwirth
58

Nie!

Przede wszystkim nie ma znaczenia, gdzie w poleceniu następuje przekierowanie. Więc jeśli podoba Ci się przekierowanie na lewo od polecenia, to w porządku:

< somefile command

jest taki sam jak

command < somefile

Po drugie, gdy używasz potoku , występuje n + 1 procesów i podpowłoka. Jest zdecydowanie wolniejszy. W niektórych przypadkach n byłoby równe zero (na przykład, gdy przekierowujesz do wbudowanej powłoki), więc używając cat, dodajesz nowy proces całkowicie niepotrzebnie.

Ogólnie rzecz biorąc, za każdym razem, gdy używasz fajki, warto poświęcić 30 sekund na sprawdzenie, czy możesz ją wyeliminować. (Ale prawdopodobnie nie warto zajmować więcej niż 30 sekund.) Oto kilka przykładów, w których rury i procesy są często używane niepotrzebnie:

for word in $(cat somefile);  # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

Zapraszam do edycji, aby dodać więcej przykładów.

kojiro
źródło
2
Cóż, wzrost prędkości nie będzie duży.
Dakkaron
9
umieszczenie "<somefile" przed "poleceniem" technicznie daje ci od lewej do prawej, ale powoduje to niejednoznaczne czytanie, ponieważ nie ma rozgraniczenia składniowego: < cat grep dogjest wymyślnym przykładem pokazującym, że nie możesz łatwo odróżnić pliku wejściowego, polecenia który otrzymuje dane wejściowe i argumenty polecenia.
nekromanta
2
Podstawową zasadą, którą przyjąłem przy podejmowaniu decyzji, dokąd zmierza przekierowanie STDIN, jest robienie wszystkiego, co minimalizuje pozory dwuznaczności / możliwości zaskoczenia. Dogmatyczne powiedzenie, że dzieje się to wcześniej, wywołuje problem nekromanty, ale dogmatyczne stwierdzenie, że idzie dalej, może zrobić to samo. Rozważmy: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). P. Czy ma <quxzastosowanie do foo, lub do bazktórego jest -execcel foo? A. Dotyczy foo, ale może wydawać się niejednoznaczny. W tym przypadku wstawianie <qux przed foo jest wyraźniejsze, choć mniej powszechne i jest analogiczne do wstawiania na końcu ENV=VAR quux.
Mark G.
3
@necromancer, <"cat" grep dogjest tam łatwiejszy do odczytania. (Zwykle jestem zwolennikiem białych znaków, ale ten konkretny przypadek jest bardzo wyjątkiem).
Charles Duffy,
1
@kojiro "Jest zdecydowanie wolniej." Nie możesz tego napisać bez poparcia tego liczbami. Moje liczby są tutaj: oletange.blogspot.com/2013/10/useless-use-of-cat.html (i pokazują, że jest wolniejszy tylko wtedy, gdy masz wysoką przepustowość). Gdzie są twoje?
Ole Tange
30

Nie zgadzam się z większością przypadków nadmiernie zadowolonej z siebie nagrody UUOC, ponieważ podczas nauczania kogoś innego catjest wygodnym miejscem na dowolne polecenie lub skomplikowany potok poleceń, które generują dane wyjściowe odpowiednie dla omawianego problemu lub zadania.

Jest to szczególnie prawdziwe w witrynach takich jak Stack Overflow, ServerFault, Unix i Linux lub w dowolnej witrynie SE.

Jeśli ktoś konkretnie pyta o optymalizację lub jeśli masz ochotę dodać dodatkowe informacje na jej temat, to świetnie, porozmawiaj o tym, że korzystanie z cat jest nieefektywne. Ale nie krytykuj ludzi, ponieważ zdecydowali się dążyć do prostoty i łatwości zrozumienia w swoich przykładach, zamiast patrzeć na mnie, jak fajnie-jestem! złożoność.

Krótko mówiąc, ponieważ kot nie zawsze jest kotem.

Również dlatego, że większość ludzi, którzy lubią nagradzać UUOC, robi to, ponieważ bardziej zależy im na tym, by pokazać, jak `` sprytni '' są, niż o pomaganie lub nauczanie ludzi. W rzeczywistości pokazują, że są prawdopodobnie kolejnym nowicjuszem, który znalazł mały kij do pokonania swoich rówieśników.


Aktualizacja

Oto kolejny UUOC, który zamieściłem w odpowiedzi na https://unix.stackexchange.com/a/301194/7696 :

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

Pedanci UUOC powiedzieliby, że to jest UUOC, ponieważ łatwo można ustawić $filterdomyślny pusty ciąg i mieć ifinstrukcję, filter='| grep -v "^$"'ale IMO, nie osadzając znaku potoku w $filter, to „bezużyteczne” catsłuży niezwykle użytecznemu celowi samodokumentowania faktu że $filterten printfwiersz nie jest tylko kolejnym argumentem sqlplus, jest to opcjonalny filtr wyjściowy wybierany przez użytkownika.

Jeśli nie ma żadnej potrzeby, aby mieć wiele opcjonalnych filtrów wyjściowych, przetwarzanie może po prostu opcja dołączania | whateverdo $filtertak często jak potrzeba - jeden dodatkowy catw rurociągu nie będzie bolało cokolwiek lub powoduje zauważalny spadek wydajności.

cas
źródło
11
Tak na marginesie -- == środku[ ] nie jest określony przez POSIX i nie wszystkie implementacje go akceptują. Standardowy operator jest sprawiedliwy =.
Charles Duffy,
27

Z wersją UUoC, catmusi odczytać plik do pamięci, a następnie zapisać go do rury, a komenda musi odczytać dane z rury, więc jądro musi skopiować cały plik trzy razy, podczas gdy w przekierowanego wypadku jądro musi tylko raz skopiować plik. Szybciej jest zrobić coś raz, niż zrobić to trzy razy.

Za pomocą:

cat "$@" | command

jest zupełnie innym i niekoniecznie bezużytecznym zastosowaniem cat. Nadal jest bezużyteczne, jeśli polecenie jest standardowym filtrem, który akceptuje zero lub więcej argumentów nazw plików i przetwarza je po kolei. Rozważ trpolecenie: jest to czysty filtr, który ignoruje lub odrzuca argumenty nazwy pliku. Aby przesłać do niego wiele plików, musisz użyć, catjak pokazano. (Oczywiście istnieje osobna dyskusja, że ​​projekt trnie jest zbyt dobry; nie ma prawdziwego powodu, dla którego nie mógł zostać zaprojektowany jako standardowy filtr). Może to również być poprawne, jeśli chcesz, aby polecenie traktowało wszystkie dane wejściowe jako pojedynczy plik, a nie jako wiele oddzielnych plików, nawet jeśli polecenie zaakceptowałoby wiele oddzielnych plików: na przykład wcjest takim poleceniem.

Jest to cat single-fileprzypadek, który jest bezwarunkowo bezużyteczny.

Jonathan Leffler
źródło
26

W obronie kota:

Tak,

   < input process > output 

lub

   process < input > output 

jest bardziej wydajne, ale wiele wywołań nie ma problemów z wydajnością, więc nie przejmujesz się.

przyczyny ergonomiczne:

Jesteśmy przyzwyczajeni do czytania od lewej do prawej, więc polecenie takie jak

    cat infile | process1 | process2 > outfile

jest trywialne do zrozumienia.

    process1 < infile | process2 > outfile

musi przeskoczyć proces1, a następnie czytać od lewej do prawej. Można to wyleczyć:

    < infile process1 | process2 > outfile

wygląda jakoś, jakby była strzałka skierowana w lewo, gdzie nic nie ma. Bardziej zagmatwany i wyglądający jak fantazyjny cytat jest:

    process1 > outfile < infile

a generowanie skryptów jest często procesem iteracyjnym,

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

gdzie widzisz swój postęp krok po kroku, podczas gdy

    < file 

nawet nie działa. Proste sposoby są mniej podatne na błędy, a ergonomiczne wyszukiwanie poleceń jest proste dzięki cat.

Innym tematem jest to, że większość ludzi była narażona na działanie> i <jako operatorów porównania, na długo przed użyciem komputera i podczas korzystania z komputera jako programistów, są one znacznie częściej narażone na takie działanie.

Porównywanie dwóch operandów z <i> jest przeciwstawne, co oznacza

(a > b) == (b < a)

Obawiałem się, że pierwszy raz użyłem <do przekierowania danych wejściowych

a.sh < file 

może oznaczać to samo co

file > a.sh

i jakoś nadpisuję mój skrypt a.sh. Może jest to problem dla wielu początkujących.

rzadkie różnice

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

Ta ostatnia może być używana bezpośrednio w obliczeniach.

factor $(cat journal.txt | wc -c)

Oczywiście <może być tutaj również użyte zamiast parametru pliku:

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

ale kogo to obchodzi - 15k?

Gdybym od czasu do czasu napotkał jakieś problemy, z pewnością zmieniłbym swój nawyk wzywania kota.

Podczas używania bardzo dużych lub wielu, wielu plików unikanie cat jest w porządku. W większości pytań użycie kota jest ortogonalne, nie na temat, a nie problem.

Rozpoczynanie tych bezużytecznych dyskusji o kotach na co drugi temat powłoki jest tylko irytujące i nudne. Rób sobie życie i czekaj na swoją minutę sławy, odpowiadając na pytania dotyczące wydajności.

nieznany użytkownik
źródło
5
+11111 .. Jako autorka aktualnie przyjętej odpowiedzi bardzo polecam to urocze uzupełnienie. Konkretne przykłady wyjaśniają moje często abstrakcyjne i rozwlekłe argumenty, a śmiech, jaki wywołuje wczesne obawy autora, file > a.shjest sam w sobie wart przeczytania tego :) Dzięki za podzielenie się!
nekromanta
W tym wezwaniem cat file | wc -c, wcmusi odczytać stdin, dopóki EOF, liczenie bajtów. Ale w tym wc -c < fileprzypadku po prostu stdin stdin, dowiaduje się, że jest to zwykły plik i wyświetla st_size zamiast czytać jakiekolwiek wejście. W przypadku dużego pliku różnica w wydajności byłaby wyraźnie widoczna.
oguz ismail
18

Dodatkowym problemem jest to, że rura może bezgłośnie maskować podpowłokę. W tym przykładzie, będę wymieniać catz echo, ale ten sam problem istnieje.

echo "foo" | while read line; do
    x=$line
done

echo "$x"

Możesz spodziewać xsię, że zawiera foo, ale tak nie jest. xTy zestaw był w podpowłoce zrodził wykonać whilepętlę.xw powłoce, która uruchomiła potok, ma niepowiązaną wartość lub nie jest w ogóle ustawiona.

W bash4 możesz skonfigurować niektóre opcje powłoki, aby ostatnie polecenie potoku było wykonywane w tej samej powłoce, co ta, która uruchamia potok, ale możesz spróbować tego

echo "foo" | while read line; do
    x=$line
done | awk '...'

i xjest ponownie lokalna dla whilepodpowłoki '.

Chepner
źródło
5
W powłokach ściśle POSIXowych może to być trudny problem, ponieważ nie ma tutaj łańcuchów ani podstawień procesów, aby uniknąć potoku. BashFAQ 24 ma kilka przydatnych rozwiązań nawet w tym przypadku.
kojiro
4
W niektórych powłokach pokazana rura nie tworzy podpowłoki. Przykłady obejmują Korn i Z. Obsługują również podstawianie procesów i tutaj łańcuchy. Oczywiście nie są one ściśle zgodne z POSIX. Bash 4 musi shopt -s lastpipeunikać tworzenia podpowłoki.
Wstrzymano do odwołania.
13

Jako ktoś, kto regularnie zwraca uwagę na to i wiele innych antywzorów programowania powłoki, czuję się zobowiązany, z opóźnieniem, rozważyć.

Skrypt powłoki jest w dużej mierze językiem kopiuj / wklej. Dla większości ludzi, którzy piszą skrypty powłoki, nie uczą się języka; jest to po prostu przeszkoda, którą muszą pokonać, aby kontynuować pracę w języku (językach), które w rzeczywistości są nieco zaznajomione.

W tym kontekście uważam, że propagowanie różnych anty-wzorców skryptów powłoki jest destrukcyjne, a nawet destrukcyjne. Idealnie byłoby, gdyby kod, który ktoś znalazł w Stack Overflow, był możliwy do skopiowania / wklejenia do ich środowiska z minimalnymi zmianami i niepełnym zrozumieniem.

Wśród wielu zasobów skryptów powłoki w sieci, Stack Overflow jest niezwykłe, ponieważ użytkownicy mogą pomóc w kształtowaniu jakości witryny, edytując pytania i odpowiedzi w witrynie. Jednak edycje kodu mogą być problematyczne ponieważ łatwo jest wprowadzić zmiany, które nie były zamierzone przez autora kodu. Dlatego mamy tendencję do zostawiania komentarzy, aby zasugerować zmiany w kodzie.

UUCA i powiązane komentarze antywzorcowe są przeznaczone nie tylko dla autorów kodu, który komentujemy; są one tak samo emptor zastrzeżenie , aby pomóc czytelnikom w miejscu stać się świadomi problemów w kodzie one znaleźć tutaj.

Nie możemy mieć nadziei na osiągnięcie sytuacji, w której żadne odpowiedzi na Stack Overflow nie zalecają bezużytecznych cats (lub zmiennych niecytowanych lubchmod 777 wielu innych plag anty-wzorców), ale możemy przynajmniej pomóc w edukacji użytkownika, który ma zamiar skopiować / wklej ten kod do najbardziej wewnętrznej ścisłej pętli ich skryptu, który jest wykonywany miliony razy.

Jeśli chodzi o przyczyny techniczne, tradycyjna mądrość jest taka, że ​​powinniśmy starać się minimalizować liczbę procesów zewnętrznych; jest to nadal dobra ogólna wskazówka podczas pisania skryptów powłoki.

tripleee
źródło
2
Również w przypadku dużych plików przepuszczanie potoku catwiąże się z wieloma dodatkowymi przełącznikami kontekstu i przepustowością pamięci (i zanieczyszczeniem pamięci podręcznej L3 dodatkowymi kopiami danych w catbuforze odczytu i buforach potoków). Zwłaszcza na dużej maszynie wielordzeniowej (takiej jak wiele konfiguracji hostingu) przepustowość pamięci podręcznej / pamięci jest zasobem współdzielonym.
Peter Cordes,
1
@PeterCordes Prześlij swoje pomiary. Tak więc możemy, jeśli to naprawdę ma znaczenie w praktyce. Z mojego doświadczenia wynika, że ​​normalnie nie ma to znaczenia: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange
1
Twój własny blog pokazuje 50% spowolnienie dla wysokiej przepustowości, a ty nawet nie patrzysz na wpływ na całkowitą przepustowość (jeśli masz rzeczy, które zajmują inne rdzenie). Jeśli się do tego zabiorę, mógłbym przeprowadzić twoje testy, podczas gdy x264 lub x265 kodują wideo przy użyciu wszystkich rdzeni i zobaczyć, jak bardzo spowalnia kodowanie wideo. kompresja bzip2i gzipkompresja są bardzo powolne w porównaniu z ilością narzutów catdo tego samego (z maszyną bezczynną). Trudno jest odczytać tabele (zawijanie wiersza w środku liczby?). sysczas znacznie się wydłuża, ale wciąż mały w porównaniu z użytkownikiem czy rzeczywistą?
Peter Cordes,
8

Często używam cat file | myprogramw przykładach. Czasami jestem oskarżany o bezużyteczne wykorzystanie kota ( http://porkmail.org/era/unix/award.html ). Nie zgadzam się z następujących powodów:

  • Łatwo jest zrozumieć, co się dzieje.

    Czytając polecenie UNIX, oczekujesz polecenia, po którym następują argumenty, po których następuje przekierowanie. Możliwe jest umieszczenie przekierowania w dowolnym miejscu, ale jest to rzadko spotykane - w ten sposób ludzie będą mieli trudniej przeczytać przykład. wierzę

    cat foo | program1 -o option -b option | program2

    jest łatwiejszy do odczytania niż

    program1 -o option -b option < foo | program2

    Jeśli przeniesiesz przekierowanie na początek, dezorientujesz ludzi, którzy nie są przyzwyczajeni do tej składni:

    < foo program1 -o option -b option | program2

    a przykłady powinny być łatwe do zrozumienia.

  • Łatwo to zmienić.

    Jeśli wiesz, że program może czytać cat, możesz normalnie założyć, że może on odczytać dane wyjściowe z dowolnego programu, który wysyła do STDOUT, a zatem możesz dostosować go do własnych potrzeb i uzyskać przewidywalne wyniki.

  • Podkreśla, że ​​program nie zawiedzie, jeśli STDIN nie jest plikiem.

    Nie jest bezpiecznie założyć, że jeśli program1 < fooprace następnie cat foo | program1będzie również pracować. Jednak można bezpiecznie założyć coś przeciwnego. Ten program działa, jeśli STDIN jest plikiem, ale kończy się niepowodzeniem, jeśli wejście jest potokiem, ponieważ używa funkcji seek:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

Koszt wydajności

Dodatkowa opłata jest płatna cat. Aby dać wyobrażenie o tym, ile przeprowadziłem kilka testów, aby zasymulować linię bazową ( cat), niską przepustowość ( bzip2), średnią przepustowość ( gzip) i wysoką przepustowość ( grep).

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

Testy zostały przeprowadzone na low-endowym systemie (0,6 GHz) i zwykłym laptopie (2,2 GHz). Przeprowadzono je 10 razy w każdym systemie i wybrano najlepszy czas, aby naśladować optymalną sytuację dla każdego testu. ISO ISO to ubuntu-11.04-desktop-i386.iso. (Ładniejsze tabele tutaj: http://oletange.blogspot.com/2013/10/useless-use-of-cat.html )

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

Wyniki pokazują, że przy małej i średniej przepustowości koszt jest rzędu 1%. Jest to dobrze mieszczące się w zakresie niepewności pomiarów, więc w praktyce nie ma różnicy.

W przypadku dużej przepustowości różnica jest większa i istnieje wyraźna różnica między nimi.

Prowadzi to do wniosku: <zamiast cat |if:

  • złożoność przetwarzania jest podobna do prostego grepa
  • wydajność jest ważniejsza niż czytelność.

W przeciwnym razie nie ma znaczenia, czy używasz, <czycat | .

Dlatego też powinieneś przyznać nagrodę UUoC tylko wtedy, gdy:

  • możesz zmierzyć znaczącą różnicę w wydajności (opublikuj swoje pomiary, gdy przyznasz nagrodę)
  • wydajność jest ważniejsza niż czytelność.
Ole Tange
źródło
-3

Myślę, że (w tradycyjny sposób) użycie potoku jest nieco szybsze; na moim pudełku użyłem stracepolecenia, aby zobaczyć, co się dzieje:

Bez rury:

toc@UnixServer:~$ strace wc -l < wrong_output.c
execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
brk(0)                                  = 0x8b50000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb779f000, 8192, PROT_READ)   = 0
mprotect(0x804f000, 4096, PROT_READ)    = 0
mprotect(0xb77ce000, 4096, PROT_READ)   = 0
munmap(0xb77a5000, 29107)               = 0
brk(0)                                  = 0x8b50000
brk(0x8b71000)                          = 0x8b71000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb77ac000, 4096)                = 0
open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
close(3)                                = 0
open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
close(3)                                = 0
read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
read(0, "", 16384)                      = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
write(1, "13\n", 313
)                     = 3
close(0)                                = 0
close(1)                                = 0
munmap(0xb7260000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?

I z rurą:

toc@UnixServer:~$ strace cat wrong_output.c | wc -l
execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
brk(0)                                  = 0xa017000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb773d000, 8192, PROT_READ)   = 0
mprotect(0x8051000, 4096, PROT_READ)    = 0
mprotect(0xb776c000, 4096, PROT_READ)   = 0
munmap(0xb7743000, 29107)               = 0
brk(0)                                  = 0xa017000
brk(0xa038000)                          = 0xa038000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
close(3)                                = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
read(3, "", 32768)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
13

Możesz wykonać trochę testów stracei timepolecenia z coraz dłuższymi poleceniami, aby uzyskać dobre testy porównawcze.

TOC
źródło
9
Nie rozumiem, co masz na myśli przez (tradycyjny sposób) użycie potoku lub dlaczego uważasz, że to stracepokazuje, że jest szybsze - stracenie śledzi wc -lwykonania w drugim przypadku. Tutaj śledzi tylko pierwsze polecenie rurociągu.
kojiro
@kojiro: mam na myśli tradycyjny sposób = najczęściej używany sposób (myślę, że używamy potoku bardziej niż pośredniego), nie mogę potwierdzić, że jest szybszy, czy nie, w moim śledzeniu widziałem więcej wezwań systemowych do pośrednictwa. Możesz użyć programu ac i pętli, aby zobaczyć, że jeden zużywa więcej czasu. Jeśli jesteś zainteresowany, możemy to umieścić tutaj :)
TOC
3
Porównywania jabłek do jabłek byłoby umieścić strace -f sh -c 'wc -l < wrong_output.c'obok strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy,
5
Oto wyniki z ideone.com, które obecnie zdecydowanie opowiadają się za bez cat: ideone.com/2w1W42#stderr
tripleee
1
@CharlesDuffy: mkfifotworzy nazwaną potokę . Anonimowa rura jest konfigurowana za pomocą pipe(2)rozwidlenia, a następnie rozwidlenia, a rodzic i dziecko zamykają różne końce rury. Ale tak, ta odpowiedź jest totalnie nonsensowna i nawet nie próbowałem liczyć wywołań systemowych ani używać strace -Odo mierzenia narzutów, ani -roznaczać czasu każdego połączenia w odniesieniu do ostatniego ...
Peter Cordes