Czy powinienem dbać o niepotrzebne koty?

50

Wiele narzędzi wiersza polecenia może pobierać dane wejściowe z potoku lub jako argument nazwy pliku. W przypadku skryptów o długiej powłoce rozpoczęcie łańcucha od znaku catsprawia, że ​​jest on bardziej czytelny, zwłaszcza jeśli pierwsze polecenie wymaga wieloliniowych argumentów.

Porównać

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

i

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

Czy ta ostatnia metoda jest mniej wydajna? Jeśli tak, czy różnica jest wystarczająca, aby się martwić, czy skrypt zostanie uruchomiony, powiedzmy, raz na sekundę? Różnica w czytelności nie jest ogromna.

tshepang
źródło
30
Spędzam znacznie więcej czasu, obserwując, jak ludzie atakują się nawzajem w związku z bezużytecznym wykorzystaniem kota na tej stronie, niż mój system faktycznie uruchamia procesy kotów
Michael Mrozek
4
@Michael: 100% się zgadza. Do diabła, zajęło mi więcej czasu, aby połączyć się ze starą nagrodą usenet raz, niż mój komputer kiedykolwiek zmarnuje tworzenie instancji cat. Myślę jednak, że większym pytaniem jest tutaj czytelność kodu, który często ma pierwszeństwo przed wydajnością. Skoro szybciej można pisać ładniej , dlaczego nie? Wskazanie problemu catzwykle prowadzi do lepszego zrozumienia przez użytkownika rurociągów i procesów. Warto spróbować, więc następnym razem piszą zrozumiały kod.
Caleb
3
Mam inny powód, dla którego nie podoba mi się pierwsza forma - jeśli chcesz dodać kolejne polecenie na początku potoku, musisz także przenieść argument, więc edycja jest bardziej denerwująca. (Oczywiście nie oznacza to, że musisz korzystać cat; punkt Caleba na temat używania funkcji i przekierowywania również to rozwiązuje.)
Cascabel,
Powiązane: Usunąć bezużyteczne zastosowania kota, czy nie?   (Meta)
G-Man mówi „Przywróć Monikę”
1
Jest wieczór w pracy, moja odmawia pracy. Otwieram przepełnienie stosu i znajduję pytanie zatytułowane „Czy powinienem dbać o niepotrzebne koty?” i zobacz kilka bezdomnych zwierząt i programistę, zastanawiających się nad ich karmieniem czy nie ...
Boris Burkov

Odpowiedzi:

46

„Ostateczna” odpowiedź jest oczywiście dostarczana przez The Useless Use of catAward .

Celem cat jest łączenie plików (lub „catenate”). Jeśli jest to tylko jeden plik, połączenie go z niczym nie jest stratą czasu i kosztuje proces.

Utworzenie instancji cat tylko po to, by Twój kod odczytywał inaczej, zapewnia jeszcze jeden proces i jeszcze jeden zestaw strumieni wejściowych / wyjściowych, które nie są potrzebne. Zazwyczaj prawdziwym problemem w skryptach będą nieefektywne pętle i przetwarzanie. W większości nowoczesnych systemów jeden dodatkowy catnie zabije twojej wydajności, ale prawie zawsze istnieje inny sposób na napisanie kodu.

Jak zauważyłeś, większość programów jest w stanie zaakceptować argument dla pliku wejściowego. Jednak zawsze istnieje wbudowana powłoka, <której można użyć wszędzie tam, gdzie oczekuje się strumienia STDIN, co pozwoli ci zaoszczędzić jeden proces, wykonując pracę w już uruchomionym procesie powłoki.

Możesz nawet wykazać się kreatywnością GDZIE to piszesz. Zwykle byłby umieszczony na końcu polecenia przed określeniem przekierowań wyjściowych lub potoków takich jak to:

sed s/blah/blaha/ < data | pipe

Ale nie musi tak być. Może nawet być na pierwszym miejscu. Na przykład twój przykładowy kod może być napisany w następujący sposób:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

Jeśli zależy Ci na czytelności skryptu, a kod jest na tyle bałaganiarski, że dodanie wiersza dla catma ułatwić śledzenie, istnieją inne sposoby wyczyszczenia kodu. Jednym z nich, z którego często korzystam, co ułatwia późniejsze rozpoznawanie skryptów, jest dzielenie potoków na logiczne zestawy i zapisywanie ich w funkcjach. Kod skryptu staje się wtedy bardzo naturalny, a każda część potoku jest łatwiejsza do debugowania.

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

Następnie możesz kontynuować fix_blahs < data | fix_frogs | reorder | format_for_sql. Ścieżka, która tak czyta, jest naprawdę łatwa do naśladowania, a poszczególne komponenty można łatwo debugować w odpowiednich funkcjach.

Caleb
źródło
26
Nie wiedziałem, że to <filemoże nastąpić przed rozkazem. To rozwiązuje wszystkie moje problemy!
3
@Tim: Bash i Zsh obsługują to, chociaż myślę, że to brzydkie. Kiedy martwię się, że mój kod jest ładny i łatwy w utrzymaniu, zwykle używam funkcji, aby go wyczyścić. Zobacz moją ostatnią edycję.
Caleb
8
@Tim <filemoże przyjść w dowolnym miejscu wiersza poleceń: <file grep needlelub grep <file needlelub grep needle <file. Wyjątkiem są złożone polecenia, takie jak pętle i grupowanie; tam przekierowanie musi nastąpić po zamknięciu done/ }/ )/ itp. @Caleb Dotyczy to wszystkich powłok Bourne / POSIX. I nie zgadzam się, że to brzydkie.
Gilles „SO- przestań być zły”
9
@Gilles w bash można zastąpić $(cat /some/file)z $(< /some/file), który robi to samo, ale unika tarła proces.
cjm
3
Aby potwierdzić, że $(< /some/file)ma ograniczoną przenośność. Działa w trybie bash, ale nie na przykład popiołu BusyBox lub sh FreeBSD. Prawdopodobnie też nie działa w desce rozdzielczej, ponieważ te trzy ostatnie muszle są bliskimi kuzynami.
dubiousjim
22

Oto podsumowanie niektórych wad:

cat $file | cmd

koniec

< $file cmd
  • Po pierwsze, uwaga: brakuje (celowo dla celów dyskusji) podwójnych cudzysłowów $filepowyżej. W przypadku catjest to zawsze problem, z wyjątkiem zsh; w przypadku przekierowania jest to tylko problem dla bashlub, ksh88a dla niektórych innych powłok tylko w trybie interaktywnym (nie w skryptach).
  • Najczęściej wymienianą wadą jest spawnowanie dodatkowego procesu. Zauważ, że jeśli cmdjest wbudowany, to nawet 2 procesy w niektórych powłokach, takich jak bash.
  • Nadal na froncie wydajności, z wyjątkiem powłok, w których catjest wbudowany, że wykonywane jest także dodatkowe polecenie (i oczywiście ładowane i inicjowane (oraz biblioteki, z którymi jest powiązany)).
  • Jeszcze na froncie wydajności, w przypadku dużych plików, oznacza to, że system będzie musiał zaplanować przemian cati cmdprocesów oraz stale napełnić i opróżnić bufor rury. Nawet jeśli cmdrobi 1GBduży read()układ połączeń na raz, kontrola będzie musiał przejść tam iz powrotem pomiędzy cata cmdponieważ rura nie może posiadać więcej niż kilka kilobajtów danych na raz.
  • Niektórzy cmd(jak wc -c) mogą dokonać optymalizacji, gdy ich standardowe wejście jest zwykłym plikiem, z którym nie mogą cat | cmdsobie poradzić, ponieważ ich standardowe wejście jest wtedy tylko potokiem. Za pomocą cati potoku oznacza to również, że nie mogą one seek()znajdować się w pliku. W przypadku poleceń takich jak taclub tail, ma to ogromną różnicę w wydajności, ponieważ oznacza to, że wraz z catnimi muszą przechowywać całe dane wejściowe w pamięci.
  • cat $file, A nawet jego bardziej poprawna wersja cat -- "$file"nie będzie działać prawidłowo w przypadku niektórych określonych nazw plików, takich jak -(lub --helpczy cokolwiek zaczynając -przypadku zapomnienia --). Jeśli ktoś nalega na użycie cat, prawdopodobnie powinien cat < "$file" | cmdzamiast tego użyć niezawodności.
  • Jeśli $filenie można go otworzyć do odczytu (odmowa dostępu, nie istnieje ...), < "$file" cmdzgłosi spójny komunikat o błędzie (przez powłokę) i nie uruchomi się cmd, podczas gdy cat $file | cmdnadal będzie działać, cmdale ze standardowym stdin wygląda jak pusty plik. Oznacza to również, że w takie rzeczy < file cmd > file2, file2nie jest niszczona, jeśli filenie można otworzyć.
Stéphane Chazelas
źródło
2
Jeśli chodzi o wydajność: ten test pokazuje, że różnica jest rzędu 1 proc., Chyba że wykonujesz bardzo małe przetwarzanie w strumieniu oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange
2
@OleTange. Oto kolejny test: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Obraz zawiera wiele parametrów. Kara za wyniki może wynosić od 0 do 100%. W każdym razie nie sądzę, że kara może być ujemna.
Stéphane Chazelas
2
wc -cjest dość wyjątkowym przypadkiem, ponieważ ma skrót. Jeśli zrobisz wc -wto, jest to porównywalne z grepmoim przykładem (tj. Bardzo małe przetwarzanie - w takiej sytuacji „<” może mieć znaczenie).
Ole Tange
@OleTange, nawet ( wc -wna rzadkim pliku 1 GB w lokalizacji C na Linuksie 4.9 amd64), to okazuje się, że podejście cat zajmuje 23% więcej czasu w systemie wielordzeniowym i 5% podczas wiązania ich z jednym rdzeniem. Pokazuje dodatkowe koszty związane z dostępem do danych przez więcej niż jeden rdzeń. Prawdopodobnie uzyskasz różne wyniki, jeśli zmienisz rozmiar potoku, użyjesz różnych danych, zaangażujesz prawdziwe operacje we / wy, użyje implementacji cat używającej splice () ... Wszystko to potwierdza, że ​​na zdjęciu pojawia się wiele parametrów i to w żadnym wypadku catnie pomoże.
Stéphane Chazelas
1
Dla mnie z plikiem 1 GB wc -wróżnica wynosi około 2% ... 15% różnicy, jeśli jest to prosty prosty grep. Co dziwne, jeśli jest w udziale plików NFS, to jest o 20% szybszy do odczytania, jeśli jest przesyłany strumieniowocat ( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Dziwne ...
rogerdpack
16

Umieszczenie <filena końcu potoku jest mniej czytelne niż cat filena początku. Naturalny angielski czyta od lewej do prawej.

Umieszczenie <fileA początek rurociągu jest mniej czytelny niż kot, powiedziałbym. Słowo jest bardziej czytelne niż symbol, zwłaszcza symbol, który wydaje się wskazywać w niewłaściwy sposób.

Użycie catzachowuje command | command | commandformat.

Jim
źródło
Zgadzam się, użycie <raz powoduje, że kod staje się mniej czytelny, ponieważ niszczy spójność składni multipipeline.
A.Danischewski
@Jim Możesz rozwiązać problem czytelności, tworząc alias <podobny do tego: alias load='<'a następnie użyj np load file | sed .... Aliasów można używać w skryptach po uruchomieniu shopt -s expand_aliases.
niieani
1
Tak, wiem o pseudonimach. Jednak chociaż ten alias zastępuje symbol słowem, wymaga on od czytelnika znajomości osobistego ustawienia aliasu, więc nie jest zbyt przenośny.
Jim
8

Jedną z rzeczy, na które inne odpowiedzi tutaj nie wydają się bezpośrednio dotyczyć, jest to, że cattakie użycie nie jest „bezużyteczne” w tym sensie, że „pojawia się zewnętrzny proces kota, który nie działa”; jest bezużyteczny w tym sensie, że „spawnowany jest proces kota, który wykonuje tylko niepotrzebną pracę”.

W przypadku tych dwóch:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

powłoka uruchamia proces sed, który odczytuje odpowiednio z pliku tymczasowego lub standardowego, a następnie wykonuje pewne przetwarzanie - odczytuje, dopóki nie trafi do nowej linii, zastępuje pierwsze „foo” (jeśli istnieje) w tym wierszu „paskiem”, a następnie drukuje tę linię na standardowe wyjście i pętle.

W przypadku:

cat somefile | sed 's/foo/bar/'

Powłoka spawnuje proces kota i proces sed, a przewody stdout kota łączą ze stdin sed. Proces cat odczytuje z pliku kilka kilogramów, a może mega-bajtów, a następnie zapisuje je na standardowym wyjściu, gdzie sommand sed odbiera stamtąd, jak w drugim przykładzie powyżej. Podczas gdy sed przetwarza ten fragment, kot czyta inny fragment i zapisuje go w swoim standardzie, aby sed mógł dalej pracować.

Innymi słowy, dodatkowa praca wymagana przez dodanie catpolecenia to nie tylko dodatkowa praca polegająca na spawnowaniu dodatkowego catprocesu, ale także dodatkowa praca polegająca na odczytywaniu i zapisywaniu bajtów pliku dwa razy zamiast raz. Teraz, praktycznie rzecz biorąc i na nowoczesnych systemach, nie robi to wielkiej różnicy - może sprawić, że twój system wykona kilka mikrosekund niepotrzebnej pracy. Ale jeśli chodzi o skrypt, który planujesz rozpowszechnić, potencjalnie dla osób używających go na komputerach, które są już słabo zasilane, kilka mikrosekund może zsumować się z wieloma iteracjami.

godlygeek
źródło
2
Zobacz oletange.blogspot.dk/2013/10/useless-use-of-cat.html, aby sprawdzić narzut związany z użyciem dodatkowego cat.
Ole Tange
@OleTange: Natknąłem się na to i odwiedziłem twojego bloga. (1) Podczas gdy widzę treść (głównie) w języku angielskim, widzę kilka słów w (chyba) duńskim: „Klassisk”, „Flipcard”, „Magasin”, „Mosaik”, „Sidebjælke”, „Øjebliksbillede” , „Tidsskyder”, „Blog-arkiv”, „Om mig”, „Skrevet” i „Vis kommentarer” (ale „Tweet”, „Like” i baner plików cookie są w języku angielskim). Czy wiesz o tym i czy jest to pod twoją kontrolą? (2) Mam problem z odczytaniem twoich tabel (2a), ponieważ linie siatki są niekompletne i (2b) Nie rozumiem, co masz na myśli przez „Diff (pct)”.
G-Man mówi „Przywróć Monikę”
blogspot.dk jest prowadzony przez Google. Spróbuj zastąpić blogspot.com. „Różnica (pct)” to ms catpodzielone przez ms bez catprocentowo (np. 264 ms / 216 ms = 1,22 = 122% = 22% wolniej z cat)
Ole Tange