Ci sono molte cose da considerare qui.
i=`cat input`
può essere costoso e ci sono molte variazioni tra le shell.
Questa è una funzione chiamata sostituzione dei comandi. L'idea è di memorizzare l'intero output del comando meno i caratteri newline finali nella i
variabile in memoria.
Per fare ciò, le shell fork il comando in una subshell e leggono il suo output attraverso una pipe o un socketpair. Vedi molte variazioni qui. Su un file di 50 MiB qui, posso vedere per esempio che bash è 6 volte più lento di ksh93 ma leggermente più veloce di zsh e due volte più veloce di yash
.
Il motivo principale per cui bash
è lento è che legge dalla pipe 128 byte alla volta (mentre altre shell leggono 4KiB o 8 KiB alla volta) ed è penalizzata dall'overhead della chiamata di sistema.
zsh
ha bisogno di fare un po 'di post-elaborazione per sfuggire ai byte NUL (altre shell si rompono sui byte NUL) e yash
fa un'elaborazione ancora più pesante analizzando i caratteri multi-byte.
Tutte le conchiglie devono eliminare i caratteri di fine riga finali che potrebbero eseguire in modo più o meno efficiente.
Alcuni potrebbero voler gestire i byte NUL in modo più elegante rispetto ad altri e verificarne la presenza.
Quindi una volta che hai quella grande variabile in memoria, qualsiasi manipolazione su di essa comporta generalmente l'allocazione di più memoria e la gestione dei dati.
Qui, stai passando (intendendo passare) il contenuto della variabile a echo
.
Fortunatamente, echo
è integrato nella tua shell, altrimenti l'esecuzione sarebbe probabilmente fallita con un errore troppo lungo nella lista arg . Anche in questo caso, la creazione dell'array dell'elenco di argomenti comporterà probabilmente la copia del contenuto della variabile.
L'altro problema principale nel tuo approccio di sostituzione dei comandi è che stai invocando l' operatore split + glob (dimenticando di citare la variabile).
Per questo, le shell devono trattare la stringa come una stringa di caratteri (anche se alcune shell non lo fanno e sono buggy a tale riguardo), quindi nelle localizzazioni UTF-8, ciò significa analizzare le sequenze UTF-8 (se non già fatto come yash
fa) , cerca i $IFS
caratteri nella stringa. Se $IFS
contiene spazio, tab o newline (che è il caso per impostazione predefinita), l'algoritmo è ancora più complesso e costoso. Quindi, le parole risultanti da tale scissione devono essere allocate e copiate.
La parte globale sarà ancora più costosa. Se uno qualsiasi di queste parole contengono caratteri glob ( *
, ?
, [
), allora la shell dovrà leggere il contenuto di alcune directory e fare un po 'il pattern matching costoso ( bash
's implementazione per esempio è notoriamente molto male a quello).
Se l'input contiene qualcosa del genere /*/*/*/../../../*/*/*/../../../*/*/*
, sarà estremamente costoso in quanto ciò significa elencare migliaia di directory e che può espandersi a diverse centinaia di MiB.
Quindi echo
eseguirà in genere qualche elaborazione aggiuntiva. Alcune implementazioni espandono le \x
sequenze nell'argomento che riceve, il che significa analizzare il contenuto e probabilmente un'altra allocazione e copia dei dati.
D'altra parte, OK, nella maggior parte delle shell cat
non è integrato, quindi ciò significa biforcare un processo ed eseguirlo (quindi caricare il codice e le librerie), ma dopo la prima chiamata, quel codice e il contenuto del file di input verrà memorizzato nella cache. D'altra parte, non ci sarà alcun intermediario. cat
leggerà grandi quantità alla volta e la scriverà immediatamente senza elaborarla, e non è necessario allocare grandi quantità di memoria, solo quell'unico buffer che riutilizza.
Significa anche che è molto più affidabile in quanto non soffoca sui byte NUL e non taglia i caratteri di nuova riga finali (e non fa dividere + glob, anche se puoi evitarlo citando la variabile e non espandere la sequenza di escape sebbene sia possibile evitarlo utilizzando printf
invece di echo
).
Se vuoi ottimizzarlo ulteriormente, invece di invocare cat
più volte, passa input
più volte a cat
.
yes input | head -n 100 | xargs cat
Eseguirà 3 comandi anziché 100.
Per rendere la versione variabile più affidabile, dovresti usare zsh
(altre shell non possono far fronte ai byte NUL) e farlo:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Se sai che l'input non contiene byte NUL, puoi farlo in modo affidabile POSIX (anche se potrebbe non funzionare dove printf
non è incorporato) con:
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
Ma questo non sarà mai più efficiente dell'uso cat
nel loop (a meno che l'input non sia molto piccolo).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)