Czy używanie pętli while do przetwarzania tekstu jest ogólnie uważane za złą praktykę w powłokach POSIX?
Jak zauważył Stéphane Chazelas , niektóre z powodów nieużywania pętli powłoki są koncepcyjne , niezawodność , czytelność , wydajność i bezpieczeństwo .
Ta odpowiedź wyjaśnia aspekty niezawodności i czytelności :
while IFS= read -r line <&3; do
printf '%s\n' "$line"
done 3< "$InputFile"
Dla wydajności The while
pętla i odczytu są ogromnie powolny podczas odczytu z pliku lub potoku, ponieważ odczyt shell wbudowaną czyta jeden znak naraz.
A co z aspektami koncepcyjnymi i bezpieczeństwa ?
shell
text-processing
Cuonglm
źródło
źródło
yes
zapisuje się do pliku tak szybko?bash
odczytuje jeden rozmiar bufora w czasie, spróbujdash
na przykład. Zobacz także unix.stackexchange.com/q/209123/38906Odpowiedzi:
Tak, widzimy wiele rzeczy, takich jak:
Albo gorzej:
(nie śmiej się, widziałem wiele z nich).
Ogólnie od początkujących skryptów powłoki. Są to naiwne dosłowne tłumaczenia tego, co zrobiłbyś w imperatywnych językach, takich jak C lub python, ale nie tak robisz rzeczy w powłokach, a te przykłady są bardzo nieefektywne, całkowicie niewiarygodne (potencjalnie prowadzące do problemów związanych z bezpieczeństwem) i jeśli kiedykolwiek zarządzasz aby naprawić większość błędów, kod staje się nieczytelny.
Koncepcyjnie
W języku C lub w większości innych języków bloki konstrukcyjne znajdują się tylko jeden poziom powyżej instrukcji komputerowych. Mówisz procesorowi, co robić, a następnie co robić dalej. Bierzesz procesor za rękę i zarządzasz nim mikro: otwierasz ten plik, czytasz tyle bajtów, robisz to, robisz to z nim.
Muszle są językiem wyższego poziomu. Można powiedzieć, że to nawet nie język. Są przed wszystkimi interpretatorami wiersza poleceń. Zadanie jest wykonywane przez te polecenia, które uruchamiasz, a powłoka służy wyłącznie do ich uporządkowania.
Jedną z wielkich rzeczy, które wprowadził Unix, był potok i te domyślne strumienie stdin / stdout / stderr, które domyślnie obsługują wszystkie polecenia.
Przez 45 lat nie znaleźliśmy lepszego niż ten interfejs API, aby wykorzystać moc poleceń i zmusić je do współpracy przy zadaniu. To prawdopodobnie główny powód, dla którego ludzie nadal używają dziś powłok.
Masz narzędzie tnące i transliteracyjne i możesz po prostu:
Powłoka zajmuje się tylko instalacją wodną (otwieranie plików, konfigurowanie rur, wywoływanie poleceń), a gdy wszystko jest gotowe, po prostu przepływa bez powłoki. Narzędzia wykonują swoją pracę jednocześnie, skutecznie we własnym tempie, z wystarczającą ilością buforowania, aby żadne z nich nie blokowało drugiego, jest po prostu piękne, a jednocześnie takie proste.
Wywołanie narzędzia ma jednak swój koszt (a my opracujemy to w punkcie wydajności). Narzędzia te można napisać z tysiącami instrukcji w C. Należy stworzyć proces, narzędzie należy załadować, zainicjować, a następnie wyczyścić, zniszczyć proces i poczekać.
Inwokowanie
cut
jest jak otwieranie szuflady kuchennej, weź nóż, użyj go, umyj, wysusz, włóż z powrotem do szuflady. Kiedy to zrobisz:To jest tak, jak w przypadku każdej linii pliku, pobieranie
read
narzędzia z szuflady kuchennej (bardzo niezdarnej, ponieważ nie jest do tego przeznaczone ), czytanie linii, mycie narzędzia do odczytu, wkładanie go z powrotem do szuflady. Następnie zaplanuj spotkanie dla narzędziaecho
icut
, weź je z szuflady, przywołaj je, umyj, wysusz, włóż z powrotem do szuflady i tak dalej.Niektóre z tych narzędzi (
read
aecho
) są zbudowane w większości powłok, ale że mało robi różnicę tutaj ponieważecho
icut
nadal muszą być prowadzone w osobnych procesach.To jak krojenie cebuli, ale mycie noża i wkładanie go z powrotem do szuflady kuchennej między każdym plasterkiem.
Tutaj oczywistym sposobem jest wyciągnięcie
cut
narzędzia z szuflady, pokrojenie całej cebuli i włożenie jej z powrotem do szuflady po zakończeniu całej pracy.IOW, w powłokach, szczególnie do przetwarzania tekstu, wywołujesz jak najmniej narzędzi i pozwalasz im współpracować z zadaniem, a nie uruchamiasz tysiące narzędzi w kolejności, czekając na uruchomienie, uruchomienie i oczyszczenie każdego z nich przed uruchomieniem następnego.
Dalsze czytanie w pięknej odpowiedzi Bruce'a . Wewnętrzne narzędzia do przetwarzania tekstu niskiego poziomu w powłokach (z wyjątkiem może
zsh
) są ograniczone, uciążliwe i zasadniczo nie nadają się do ogólnego przetwarzania tekstu.Występ
Jak powiedziano wcześniej, uruchomienie jednego polecenia ma swój koszt. Ogromny koszt, jeśli to polecenie nie jest wbudowane, ale nawet jeśli są wbudowane, koszt jest duży.
Powłoki nie zostały zaprojektowane do takiego działania, nie mają pretensji do bycia wydajnymi językami programowania. Nie są, są tylko interpretatorami wiersza poleceń. Tak więc na tym froncie dokonano niewielkiej optymalizacji.
Ponadto powłoki wykonują polecenia w osobnych procesach. Te bloki konstrukcyjne nie mają wspólnej pamięci ani stanu. Kiedy robisz a
fgets()
lubfputs()
w C, jest to funkcja in stdio. stdio przechowuje wewnętrzne bufory wejściowe i wyjściowe dla wszystkich funkcji stdio, aby uniknąć zbyt częstego wykonywania kosztownych wywołań systemowych.Odpowiednie nawet wbudowane narzędzia powłoki (
read
,echo
,printf
) nie może zrobić.read
ma czytać jedną linię. Jeśli odczyta znak nowego wiersza, oznacza to, że następne polecenie, które wykonasz, nie trafi.read
Musi więc czytać dane wejściowe jeden bajt na raz (niektóre implementacje mają optymalizację, jeśli dane wejściowe są zwykłym plikiem, ponieważ odczytują fragmenty i szukają wstecz, ale działa to tylko dla zwykłych plików ibash
na przykład odczytuje tylko fragmenty 128-bajtowe, co jest wciąż dużo mniej niż narzędzia tekstowe).To samo po stronie wyjściowej,
echo
nie może po prostu buforować swoich danych wyjściowych, musi je natychmiast wydrukować, ponieważ następne uruchomione polecenie nie udostępni tego bufora.Oczywiście uruchamianie poleceń sekwencyjnie oznacza, że musisz na nie poczekać, to mały taniec harmonogramu, który daje kontrolę z powłoki i narzędzi iz powrotem. Oznacza to również (w przeciwieństwie do używania długo działających instancji narzędzi w potoku), że nie można wykorzystać kilku procesorów jednocześnie, jeśli są one dostępne.
Pomiędzy tą
while read
pętlą a (podobno) ekwiwalentemcut -c3 < file
w moim szybkim teście współczynnik czasu procesora wynosi około 40000 w moich testach (jedna sekunda w porównaniu do pół dnia). Ale nawet jeśli używasz tylko wbudowanych powłok:(tutaj z
bash
), to wciąż około 1: 600 (jedna sekunda vs 10 minut).Wiarygodność / czytelność
Bardzo trudno jest poprawnie ustawić ten kod. Podane przeze mnie przykłady są zbyt często spotykane na wolności, ale zawierają wiele błędów.
read
jest poręcznym narzędziem, które może robić wiele różnych rzeczy. Może odczytywać dane wejściowe od użytkownika, dzielić je na słowa, aby przechowywać je w różnych zmiennych.read line
czy nie czytać linię wejścia, a może to czyta wiersz w bardzo szczególny sposób. W rzeczywistości odczytuje słowa z danych wejściowych, te słowa oddzielone$IFS
i gdzie można użyć odwrotnego ukośnika, aby uciec przed separatorami lub znakiem nowej linii.Z wartością domyślną
$IFS
na wejściu takim jak:read line
zapisze się"foo/bar baz"
w$line
, nie" foo\/bar \"
tak jak można się spodziewać.Aby odczytać wiersz, potrzebujesz:
To nie jest bardzo intuicyjne, ale tak właśnie jest, pamiętaj, że muszle nie były przeznaczone do takiego użycia.
To samo dotyczy
echo
.echo
rozwija sekwencje. Nie można go używać do dowolnych treści, takich jak zawartość losowego pliku. Potrzebujeszprintf
tutaj zamiast tego.I oczywiście jest typowe zapominanie o cytowaniu zmiennej, do której wszyscy wpadają. Więc to więcej:
Teraz jeszcze kilka ostrzeżeń:
zsh
tego, że to nie działa, jeśli wejście zawiera znaki NUL, podczas gdy przynajmniej narzędzia tekstowe GNU nie miałyby problemu.Jeśli chcemy rozwiązać niektóre z powyższych problemów, staje się to:
To staje się coraz mniej czytelne.
Istnieje wiele innych problemów z przekazywaniem danych do poleceń za pomocą argumentów lub odzyskiwaniem ich danych wyjściowych w zmiennych:
-
(lub+
czasami)expr
,test
...Względy bezpieczeństwa
Kiedy zaczynasz pracę ze zmiennymi powłoki i argumentami poleceń , wpisujesz pole minowe.
Jeśli zapomnisz zacytować zmienne , zapomnisz znacznika końca opcji , będziesz pracować w ustawieniach regionalnych ze znakami wielobajtowymi (obecnie jest to norma), na pewno wprowadzisz błędy, które wcześniej czy później staną się podatne na atak.
Kiedy możesz użyć pętli.
TBD
źródło
cut
na przykład jest wydajny.cut -f1 < a-very-big-file
jest wydajny, tak wydajny, jak byś napisał go w C. To, co jest strasznie nieefektywne i podatne na błędy, wywołujecut
każdą linięa-very-big-file
w pętli powłoki, o co właśnie chodzi w tej odpowiedzi. To zgadza się z twoim ostatnim stwierdzeniem o pisaniu niepotrzebnego kodu, co sprawia, że myślę, że może nie rozumiem twojego komentarza.Jeśli chodzi o koncepcję i czytelność, powłoki zazwyczaj są zainteresowane plikami. Ich „jednostką adresowalną” jest plik, a „adres” to nazwa pliku. Powłoki mają wszelkiego rodzaju metody testowania na obecność pliku, typ pliku, formatowanie nazwy pliku (zaczynając od globowania). Powłoki mają bardzo mało prymitywów do radzenia sobie z zawartością plików. Programiści powłoki muszą wywołać inny program do obsługi zawartości pliku.
Z uwagi na orientację pliku i nazwy pliku manipulowanie tekstem w powłoce jest bardzo powolne, jak zauważyłeś, ale wymaga również niejasnego i zniekształconego stylu programowania.
źródło
Istnieje kilka skomplikowanych odpowiedzi, podających wiele ciekawych szczegółów dla maniaków wśród nas, ale to naprawdę bardzo proste - przetwarzanie dużego pliku w pętli powłoki jest po prostu zbyt wolne.
Myślę, że pytający jest interesujący w typowym rodzaju skryptu powłoki, który może rozpocząć się od analizy wiersza poleceń, ustawienia środowiska, sprawdzania plików i katalogów oraz nieco większej inicjalizacji, zanim przejdzie do swojego głównego zadania: przejścia przez duże plik tekstowy zorientowany liniowo.
W przypadku pierwszych części (
initialization
) zwykle nie ma znaczenia, że polecenia powłoki są powolne - uruchamia tylko kilkadziesiąt poleceń, może z kilkoma krótkimi pętlami. Nawet jeśli piszemy tę część nieefektywnie, zwykle zajmie to mniej niż sekundę, aby wykonać całą tę inicjalizację, i to dobrze - dzieje się to tylko raz.Ale gdy mamy do przetwarzania duży plik, który może mieć tysiące lub miliony linii, to jest nie w porządku dla skrypt powłoki podjąć znaczny ułamek sekundy (nawet jeśli jest to tylko kilkadziesiąt milisekund) dla każdej linii, ponieważ to może zsumować godziny.
Właśnie wtedy musimy użyć innych narzędzi, a piękno skryptów powłoki Unix polega na tym, że ułatwiają nam to.
Zamiast używać pętli do patrzenia na każdą linię, musimy przekazać cały plik przez potok poleceń . Oznacza to, że zamiast wywoływać polecenia tysiące lub miliony razy, powłoka wywołuje je tylko raz. To prawda, że te polecenia będą miały pętle do przetwarzania pliku wiersz po wierszu, ale nie są to skrypty powłoki i zostały zaprojektowane tak, aby były szybkie i wydajne.
Unix ma wiele wspaniałych wbudowanych narzędzi, od prostych po kompleksowe, których możemy użyć do budowy naszych potoków. Zwykle zaczynałem od prostych i tylko w razie potrzeby korzystałem z bardziej złożonych.
Spróbowałbym też trzymać się standardowych narzędzi, które są dostępne w większości systemów, i starać się, aby moje użycie było przenośne, chociaż nie zawsze jest to możliwe. A jeśli twoim ulubionym językiem jest Python lub Ruby, być może nie będziesz miał nic przeciwko dodatkowemu wysiłkowi, aby upewnić się, że jest zainstalowany na każdej platformie, na której oprogramowanie musi działać :-)
Proste narzędzia obejmują
head
,tail
,grep
,sort
,cut
,tr
,sed
,join
(gdy łączenie 2 pliki) iawk
jednej wkładki, wśród wielu innych. To niesamowite, co niektórzy ludzie mogą zrobić dzięki dopasowaniu wzorców ised
poleceniom.Kiedy staje się bardziej skomplikowana i naprawdę musisz zastosować logikę do każdej linii,
awk
jest dobrą opcją - albo jednowierszowa (niektórzy ludzie umieszczają całe skrypty awk w „jednej linii”, chociaż nie jest to zbyt czytelne) lub w krótki skrypt zewnętrzny.Ponieważ
awk
jest to język interpretowany (jak twoja powłoka), to niesamowite, że potrafi tak wydajnie przetwarzać wiersz po wierszu, ale jest specjalnie do tego przeznaczony i jest naprawdę bardzo szybki.I jest
Perl
jeszcze wiele innych języków skryptowych, które są bardzo dobre w przetwarzaniu plików tekstowych, a także zawierają wiele przydatnych bibliotek.I wreszcie, jest dobry stary C, jeśli potrzebujesz maksymalnej prędkości i dużej elastyczności (chociaż przetwarzanie tekstu jest nieco nudne). Ale prawdopodobnie jest to bardzo złe wykorzystanie twojego czasu na napisanie nowego programu C dla każdego innego zadania przetwarzania plików, na jakie napotkasz. Dużo pracuję z plikami CSV, więc napisałem kilka ogólnych narzędzi w C, które mogę ponownie wykorzystać w wielu różnych projektach. W efekcie rozszerza to zakres „prostych, szybkich narzędzi uniksowych”, które mogę wywoływać ze swoich skryptów powłoki, dzięki czemu mogę obsługiwać większość projektów, pisząc tylko skrypty, co jest znacznie szybsze niż pisanie i debugowanie kodu C na zamówienie!
Kilka ostatecznych wskazówek:
export LANG=C
, w przeciwnym razie wiele narzędzi potraktuje zwykłe pliki ASCII jako Unicode, dzięki czemu będą one znacznie wolniejszeexport LC_ALL=C
jeśli chceszsort
produkować spójne zamówienia, niezależnie od środowiska!sort
swoich danych, prawdopodobnie zajmie to więcej czasu (i zasobów: procesor, pamięć, dysk) niż wszystko inne, więc spróbuj zminimalizować liczbęsort
poleceń i rozmiar sortowanych plikówźródło
Tak ale...
Poprawna odpowiedź Stéphane Chazelas opiera się na muszli pojęcia delegowania każdej operacji tekstu do określonych plików binarnych, jak
grep
,awk
,sed
i innych.Ponieważ bash jest w stanie samodzielnie robić wiele rzeczy, upuszczanie widelców może stać się szybsze (nawet niż uruchamianie innego tłumacza do wykonywania wszystkich zadań).
Na przykład, spójrz na ten post:
https://stackoverflow.com/a/38790442/1765658
i
https://stackoverflow.com/a/7180078/1765658
przetestuj i porównaj ...
Oczywiście
Nie bierze się pod uwagę wkładu użytkownika i bezpieczeństwa !
Nie pisz aplikacji internetowej pod bash !!
Ale w przypadku wielu zadań związanych z administrowaniem serwerem, gdzie bash może być użyty zamiast powłoki , użycie wbudowanego bash może być bardzo wydajne.
Moje znaczenie:
Pisanie narzędzi takich jak bin utils to nie to samo, co administracja systemem.
Więc nie ci sami ludzie!
Tam, gdzie sysadmini muszą wiedzieć
shell
, mogą pisać prototypy , korzystając z jego preferowanego (i najlepiej znanego) narzędzia.Jeśli to nowe narzędzie (prototyp) jest naprawdę przydatne, inne osoby mogłyby opracować dedykowane narzędzie, używając bardziej odpowiedniego języka.
źródło
bash
. (ponad 3 razy szybszy z ksh93 w moim teście na moim systemie).bash
jest ogólnie najwolniejszą powłoką. Nawetzsh
jest dwa razy szybszy na tym skrypcie. Masz również kilka problemów z niecytowanymi zmiennymi i użyciemread
. W rzeczywistości ilustrujesz tutaj wiele moich punktów.sh
, Awk , Sed ,grep
,ed
,ex
,cut
,sort
,join
... wszystko z większą niezawodność niż bash lub Perl.bash
domyślnie instalowana.bash
jest przeważnie tylko na Apple MacOS i systemów GNU (Przypuszczam, że to, co nazywasz główne dystrybucje ), choć wiele systemów mają również go jako opcjonalny pakiet (jakzsh
,tcl
,python
...)