Jest tu kilka rzeczy do rozważenia.
i=`cat input`
mogą być drogie i istnieje wiele odmian między pociskami.
Jest to funkcja zwana zastępowaniem poleceń. Chodzi o to, aby zapisać cały wynik polecenia minus końcowe znaki nowego wiersza w i
zmiennej w pamięci.
Aby to zrobić, powłoki rozwidlają polecenie w podpowłoce i odczytują jego dane wyjściowe przez potok lub parę gniazd. Tutaj widzisz wiele odmian. W pliku 50 MB tutaj widzę na przykład, że bash jest 6 razy wolniejszy niż ksh93, ale nieco szybszy niż zsh i dwa razy szybszy yash
.
Głównym powodem bash
spowolnienia jest to, że czyta z potoku 128 bajtów jednocześnie (podczas gdy inne powłoki odczytują jednocześnie 4KiB lub 8KiB) i jest karany przez narzut wywołania systemowego.
zsh
musi wykonać przetwarzanie końcowe, aby uniknąć bajtów NUL (inne powłoki łamią się na bajtach NUL), a yash
nawet wykonuje bardziej wymagające przetwarzanie przez analizowanie znaków wielobajtowych.
Wszystkie powłoki muszą usunąć końcowe znaki nowego wiersza, które mogą wykonywać mniej lub bardziej wydajnie.
Niektórzy mogą chcieć obsługiwać bajty NUL bardziej wdzięcznie niż inni i sprawdzać ich obecność.
Następnie, gdy masz już tę dużą zmienną w pamięci, wszelkie manipulacje nią zazwyczaj wiążą się z przydzielaniem większej ilości pamięci i kopiowaniem danych.
Tutaj przekazujesz (zamierzałeś przekazać) zawartość zmiennej do echo
.
Na szczęście echo
jest wbudowany w twoją powłokę, w przeciwnym razie wykonanie prawdopodobnie nie powiedzie się z powodu zbyt długiego błędu listy arg . Nawet wtedy zbudowanie tablicy listy argumentów prawdopodobnie będzie wymagało skopiowania zawartości zmiennej.
Innym głównym problemem w metodzie zastępowania poleceń jest to, że wywołujesz operator split + glob (zapominając o cytowaniu zmiennej).
W tym celu powłoki muszą traktować ciąg znaków jako ciąg znaków (chociaż niektóre powłoki nie mają i są pod tym względem błędne), więc w ustawieniach regionalnych UTF-8 oznacza to, że parsowanie sekwencji UTF-8 (jeśli nie jest zrobione już tak jak yash
robi) , poszukaj $IFS
znaków w ciągu. Jeśli $IFS
zawiera spację, tabulator lub znak nowej linii (co jest domyślnym przypadkiem), algorytm jest jeszcze bardziej złożony i kosztowny. Następnie słowa wynikające z tego podziału należy przypisać i skopiować.
Część glob będzie jeszcze droższa. Jeśli którykolwiek z tych słów zawierać znaków glob ( *
, ?
, [
), wówczas powłoka będzie musiał przeczytać zawartość niektórych katalogów i trochę drogie pasujące do wzorca ( bash
„s implementacja na przykład notorycznie jest bardzo zły na to).
Jeśli dane wejściowe zawierają coś podobnego /*/*/*/../../../*/*/*/../../../*/*/*
, będzie to bardzo kosztowne, ponieważ oznacza to wyświetlenie tysięcy katalogów i może wzrosnąć do kilkuset MiB.
Następnie echo
zazwyczaj wykonuje dodatkowe przetwarzanie. Niektóre implementacje rozszerzają \x
sekwencje w otrzymywanym argumencie, co oznacza parsowanie zawartości i prawdopodobnie kolejną alokację i kopię danych.
Z drugiej strony, OK, w większości powłok cat
nie jest wbudowany, więc oznacza to rozwidlenie procesu i jego wykonanie (więc załadowanie kodu i bibliotek), ale po pierwszym wywołaniu ten kod i zawartość pliku wejściowego zostaną zapisane w pamięci podręcznej. Z drugiej strony nie będzie pośrednika. cat
odczytuje duże ilości naraz i zapisuje je od razu bez przetwarzania, i nie musi przydzielać dużej ilości pamięci, tylko ten bufor, którego ponownie używa.
Oznacza to również, że jest o wiele bardziej niezawodny, ponieważ nie dusi się w bajtach NUL i nie przycina końcowych znaków nowego wiersza (i nie dzieli split + glob, chociaż można tego uniknąć, cytując zmienną, i nie rozwiń sekwencję zmiany znaczenia, ale możesz tego uniknąć, używając printf
zamiast echo
).
Jeśli chcesz dalej go optymalizować, zamiast wywoływać cat
kilka razy, po prostu przejdź input
kilka razy do cat
.
yes input | head -n 100 | xargs cat
Uruchomi 3 polecenia zamiast 100.
Aby uczynić wersję zmienną bardziej niezawodną, musisz użyć zsh
(inne powłoki nie radzą sobie z bajtami NUL) i zrobić to:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Jeśli wiesz, że dane wejściowe nie zawierają bajtów NUL, możesz to niezawodnie wykonać POSIXly (choć może nie działać, jeśli printf
nie jest wbudowane) za pomocą:
i=$(cat input && echo .) || exit # add an extra .\n to avoid trimming newlines
i=${i%.} # remove that trailing dot (the \n was removed by cmdsubst)
n=10
while [ "$n" -gt 10 ]; do
printf %s "$i"
n=$((n - 1))
done
Ale to nigdy nie będzie bardziej wydajne niż używanie cat
w pętli (chyba że dane wejściowe są bardzo małe).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)