Тут слід розглянути кілька речей.
i=`cat input`
може бути дорогим, і між оболонками може бути багато варіацій.
Це особливість, яка називається підміна команд. Ідея полягає у збереженні всього виводу команди за мінусом знаків нового рядка у i
змінну в пам'яті.
Для цього оболонки розщеплюють команду в підпакеті і читають її вихід через трубу або сокетпар. Ви бачите тут багато варіацій. У файлі 50MiB тут я бачу, наприклад, що bash у 6 разів повільніше ksh93, але трохи швидше, ніж zsh і вдвічі швидше yash
.
Основна причина bash
повільності полягає в тому, що він читає з труби 128 байтів одночасно (в той час як інші оболонки читають 4KiB або 8KiB одночасно) і карається накладними викликами системи.
zsh
потрібно виконати деяку післяобробку, щоб уникнути байтів NUL (інші оболонки розбиваються на байти NUL), а yash
ще більше обтяжує обробку, аналізуючи багатобайтові символи.
Усі оболонки повинні знімати символи нового рядка, які вони можуть робити більш-менш ефективно.
Деякі можуть захотіти обробляти байти NUL більш витончено, ніж інші, і перевірити їх наявність.
Потім, коли у вас є така велика змінна пам'ять, будь-яка маніпуляція з нею, як правило, передбачає розподіл більше пам'яті та копіювання даних поперек.
Тут ви передаєте (мали намір передати) вміст змінної в echo
.
На щастя, echo
він вбудований у вашу оболонку, інакше виконання, ймовірно, не вдалося б із списком аргументів занадто довгою помилкою. Вже тоді побудова масиву списку аргументів, можливо, передбачає копіювання вмісту змінної.
Інша основна проблема у вашому підході до заміни команди полягає в тому, що ви викликаєте оператор split + glob (забувши процитувати змінну).
Для цього оболонки повинні сприймати рядок як рядок символів (хоча деякі оболонки не мають і є помилковими в цьому відношенні), так що в локалі UTF-8, це означає розбір послідовностей UTF-8 (якщо це не зроблено вже так, як yash
це робиться) , шукайте $IFS
символів у рядку. Якщо $IFS
міститься пробіл, вкладка або новий рядок (що за замовчуванням є), алгоритм є ще складнішим і дорогішим. Потім слова, отримані в результаті розщеплення, потрібно виділити та скопіювати.
Глобальна частина буде ще дорожчою. Якщо будь-які з цих слів містять Глоби символи ( *
, ?
, [
), то оболонка буде читати вміст деяких каталогів і зробити деякий дорогою по шаблоном ( bash
реалізація «s, наприклад , як відомо , дуже погано в цьому).
Якщо вхід містить щось подібне /*/*/*/../../../*/*/*/../../../*/*/*
, це буде надзвичайно дорогим, оскільки це означає перелік тисяч каталогів і може розширитися до декількох сотень МіБ.
Тоді echo
зазвичай виконується додаткова обробка. Деякі реалізації розширюють \x
послідовності аргументу, який він отримує, що означає розбір вмісту і, можливо, інше розподілення та копіювання даних.
З іншого боку, гаразд, у більшості оболонок cat
не вбудований, тобто означає розблокувати процес та виконати його (таким чином завантаження коду та бібліотек), але після першого виклику цей код та вміст вхідного файлу збережеться в пам'яті. З іншого боку, посередника не буде. cat
буде читати великі обсяги одночасно і записувати його відразу, не обробляючи, і йому не потрібно виділяти величезну кількість пам'яті, лише той буфер, який він повторно використовує.
Це також означає, що це набагато надійніше, оскільки він не задихається на байтах NUL і не обрізає символи нового рядка (і не робить розділення + глобул, хоча ви можете цього уникнути, цитуючи змінну, і не розгорніть послідовність втечі, хоча ви можете уникнути цього, використовуючи printf
замість echo
).
Якщо ви хочете додатково оптимізувати це, замість того, щоб викликати cat
кілька разів, просто перейдіть input
до цього cat
.
yes input | head -n 100 | xargs cat
Запустить 3 команди замість 100.
Щоб зробити змінну версію більш надійною, вам потрібно буде скористатися zsh
(інші оболонки не можуть впоратися з байтами NUL) і зробити це:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Якщо ви знаєте, що вхід не містить байтів NUL, то ви можете надійно зробити це POSIXly (хоча він може не працювати там, де printf
не вбудований) за допомогою:
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
Але це ніколи не буде більш ефективним, ніж використання cat
в циклі (якщо тільки вхід не дуже малий).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)