Há várias coisas a considerar aqui.
i=`cat input`
pode ser caro e há muitas variações entre as conchas.
Esse é um recurso chamado substituição de comando. A idéia é armazenar toda a saída do comando menos os caracteres de nova linha à direita na i
variável na memória.
Para fazer isso, os shells dividem o comando em um subshell e leem sua saída através de um cano ou soquete. Você vê muita variação aqui. Em um arquivo de 50MiB aqui, posso ver, por exemplo, o bash sendo 6 vezes mais lento que o ksh93, mas um pouco mais rápido que o zsh e duas vezes mais rápido que yash
.
O principal motivo para bash
ser lento é que ele lê a partir do canal 128 bytes por vez (enquanto outros shells lêem 4KiB ou 8KiB por vez) e é penalizado pela sobrecarga de chamada do sistema.
zsh
precisa executar algum pós-processamento para escapar de bytes NUL (outros shells quebram em bytes NUL) e yash
faz um processamento ainda mais pesado analisando caracteres de vários bytes.
Todos os shells precisam retirar os caracteres de nova linha à direita, que eles podem estar executando de maneira mais ou menos eficiente.
Alguns podem querer lidar com bytes NUL mais graciosamente que outros e verificar sua presença.
Então, uma vez que você tenha essa grande variável na memória, qualquer manipulação nela geralmente envolve alocar mais memória e lidar com dados.
Aqui, você está passando (pretendia passar) o conteúdo da variável para echo
.
Felizmente, echo
está embutido no seu shell, caso contrário, a execução provavelmente falharia com um erro muito longo da lista arg . Mesmo assim, construir a matriz da lista de argumentos possivelmente envolverá a cópia do conteúdo da variável.
O outro problema principal em sua abordagem de substituição de comando é que você está chamando o operador split + glob (esquecendo de citar a variável).
Para isso, os shells precisam tratar a sequência como uma sequência de caracteres (embora alguns shells não o façam e são problemáticos a esse respeito), portanto, nos locais UTF-8, isso significa analisar as sequências UTF-8 (se ainda não o fez yash
) , procure $IFS
caracteres na sequência. Se $IFS
contiver espaço, tabulação ou nova linha (que é o caso por padrão), o algoritmo é ainda mais complexo e caro. Em seguida, as palavras resultantes dessa divisão precisam ser alocadas e copiadas.
A parte glob será ainda mais cara. Se qualquer uma dessas palavras conter caracteres glob ( *
, ?
, [
), então o shell terá que ler o conteúdo de alguns diretórios e fazer alguma correspondência de padrão caro ( bash
de implementação, por exemplo, é notoriamente muito ruim em que).
Se a entrada contiver algo como /*/*/*/../../../*/*/*/../../../*/*/*
, isso será extremamente caro, pois significa listar milhares de diretórios e pode expandir para várias centenas de MiB.
Em seguida echo
, normalmente fará algum processamento extra. Algumas implementações expandem \x
sequências no argumento recebido, o que significa analisar o conteúdo e provavelmente outra alocação e cópia dos dados.
Por outro lado, OK, na maioria dos shells cat
não é incorporado, o que significa bifurcar um processo e executá-lo (carregar o código e as bibliotecas), mas após a primeira chamada, esse código e o conteúdo do arquivo de entrada será armazenado em cache na memória. Por outro lado, não haverá intermediário. cat
lerá grandes quantidades de cada vez e gravará imediatamente, sem processamento, e não precisará alocar uma quantidade enorme de memória, apenas aquele buffer que ele reutiliza.
Isso também significa que é muito mais confiável, pois não engasga com bytes NUL e não corta caracteres de nova linha à direita (e não divide + glob, embora você possa evitar isso citando a variável e não expanda a sequência de escape, embora você possa evitá-la usando em printf
vez de echo
).
Se você deseja otimizá-lo ainda mais, em vez de chamar cat
várias vezes, basta passar input
várias vezes para cat
.
yes input | head -n 100 | xargs cat
Irá executar 3 comandos em vez de 100.
Para tornar a versão variável mais confiável, você precisará usar zsh
(outros shells não podem lidar com bytes NUL) e fazer isso:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Se você souber que a entrada não contém bytes NUL, poderá fazê-lo de maneira confiável POSIXly (embora possa não funcionar onde printf
não está embutido) com:
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
Mas isso nunca será mais eficiente do que usar cat
no loop (a menos que a entrada seja muito pequena).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)