Тут слід розглянути кілька речей.
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? :)