Há várias coisas a considerar aqui.
i='cat input'
pode ser caro e há muitas variações entre os shells.
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 variável i
na memória.
Para fazer isso, shells bifurcam o comando em um subshell e lêem sua saída através de um pipe ou par de soquetes. Você vê muita variação aqui. Em um arquivo de 50 MiB aqui, eu posso ver por exemplo que o bash é 6 vezes mais lento que o ksh93, mas um pouco mais rápido que o zsh e duas vezes mais rápido que o yash
.
A principal razão para o bash
ser lento é que ele lê do pipe 128 bytes por vez (enquanto outros shells leem 4KiB ou 8KiB por vez) e é penalizado pela sobrecarga da chamada do sistema.
zsh
precisa fazer algum pós-processamento para escapar de bytes NUL (outros shells quebram em bytes NUL), e yash
faz processamento ainda mais pesado analisando caracteres de múltiplos bytes.
Todos os shells precisam remover os caracteres de nova linha que podem estar fazendo com mais ou menos eficiência.
Alguns podem querer manipular bytes NUL mais facilmente que outros e verificar sua presença.
Então, quando você tem aquela grande variável na memória, qualquer manipulação geralmente envolve a alocação de mais memória e dados de enfrentamento.
Aqui, você está passando (pretendia passar) o conteúdo da variável para echo
.
Por sorte, echo
está embutido em seu shell, caso contrário a execução provavelmente teria falhado com um erro arg list too long . Mesmo assim, construir a matriz da lista de argumentos possivelmente envolverá copiar o conteúdo da variável.
O outro grande problema na sua abordagem de substituição de comandos é que você está invocando o operador split + glob (esquecendo-se de citar a variável).
Para isso, os shells precisam tratar a string como uma string de caracteres (apesar de alguns shells não possuírem bugs nesse sentido) então em locales UTF-8, isso significa analisar UTF- 8 sequências (se não forem feitas como yash
), procure por $IFS
caracteres na string. Se $IFS
contiver espaço, tabulação ou nova linha (que é o caso por padrão), o algoritmo será ainda mais complexo e caro. Então, as palavras resultantes dessa divisão precisam ser alocadas e copiadas.
A parte glob será ainda mais cara. Se qualquer uma dessas palavras contiverem caracteres glob ( *
, ?
, [
), o shell terá que ler o conteúdo de alguns diretórios e fazer uma correspondência de padrões cara (a implementação de bash
, por exemplo, é notoriamente muito ruim nisso).
Se a entrada contiver algo como /*/*/*/../../../*/*/*/../../../*/*/*
, isso será extremamente caro, pois isso significa listar milhares de diretórios e expandir para várias centenas de MiB.
Em seguida, echo
normalmente fará algum processamento extra. Algumas implementações expandem \x
de 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, o cat
não está integrado, o que significa que um processo é executado e executado (carregando 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 quantias por vez e as escreverá imediatamente, sem processamento, e não precisará alocar uma quantidade enorme de memória, apenas um buffer que reutilize.
Isso também significa que é muito mais confiável, já que não engasga com bytes NUL e não corta caracteres de nova linha (e não faz split + glob, embora você possa evitar isso citando a variável, e não expande a sequência de escape, embora você possa evitar isso usando printf
em vez de echo
).
Se você quiser otimizá-lo ainda mais, em vez de invocar cat
várias vezes, basta passar input
várias vezes para cat
.
yes input | head -n 100 | xargs cat
executará 3 comandos em vez de 100.
Para tornar a versão da variável mais confiável, você precisará usar zsh
(outras shells não podem lidar com bytes NUL) e faça isso:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Se você sabe que a entrada não contém bytes NUL, então você pode fazê-lo POSIXly de forma confiável (embora talvez não funcione 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).