Hay varias cosas a considerar aquí.
i=`cat input`
puede ser costoso y hay muchas variaciones entre conchas.
Esa es una característica llamada sustitución de comando. La idea es almacenar toda la salida del comando menos los caracteres de nueva línea finales en la i
variable en la memoria.
Para hacer eso, los shells bifurcan el comando en un subshell y leen su salida a través de una tubería o par de conectores. Ves mucha variación aquí. En un archivo de 50MiB aquí, puedo ver, por ejemplo, que bash es 6 veces más lento que ksh93 pero un poco más rápido que zsh y dos veces más rápido que yash
.
La razón principal de bash
ser lento es que lee 128 bytes de la tubería a la vez (mientras que otros shells leen 4KiB u 8KiB a la vez) y está penalizado por la sobrecarga de llamadas del sistema.
zsh
necesita realizar un procesamiento posterior para escapar de los bytes NUL (otros shells se rompen en los bytes NUL), y yash
realiza un procesamiento aún más pesado al analizar caracteres de varios bytes.
Todos los shells necesitan quitar los caracteres de línea nueva que pueden estar haciendo de manera más o menos eficiente.
Algunos pueden querer manejar bytes NUL con más gracia que otros y verificar su presencia.
Luego, una vez que tenga esa gran variable en la memoria, cualquier manipulación implica generalmente asignar más memoria y hacer frente a los datos.
Aquí, está pasando (tenía la intención de pasar) el contenido de la variable a echo
.
Afortunadamente, echo
está integrado en su shell, de lo contrario, la ejecución probablemente habría fallado con un error de lista arg demasiado larga . Incluso entonces, construir la matriz de la lista de argumentos posiblemente implicará copiar el contenido de la variable.
El otro problema principal en su enfoque de sustitución de comandos es que está invocando el operador split + glob (olvidando citar la variable).
Para eso, los shells deben tratar la cadena como una cadena de caracteres (aunque algunos shells no lo hacen y son defectuosos en ese sentido), por lo que en las configuraciones regionales UTF-8, eso significa analizar secuencias UTF-8 (si no se ha hecho ya yash
) , busque $IFS
caracteres en la cadena. Si $IFS
contiene espacio, tabulación o nueva línea (que es el caso por defecto), el algoritmo es aún más complejo y costoso. Luego, las palabras resultantes de esa división deben asignarse y copiarse.
La parte glob será aún más cara. Si cualquiera de esas palabras contienen caracteres glob ( *
, ?
, [
), entonces la cáscara tendrá que leer el contenido de algunos directorios y hacer un poco caro coincidencia de patrones ( bash
's aplicación, por ejemplo, es notoriamente muy mala, por cierto).
Si la entrada contiene algo como /*/*/*/../../../*/*/*/../../../*/*/*
eso, será extremadamente costoso ya que eso significa listar miles de directorios y eso puede expandirse a varios cientos de MiB.
Luego echo
, normalmente hará un procesamiento adicional. Algunas implementaciones expanden \x
secuencias en el argumento que recibe, lo que significa analizar el contenido y probablemente otra asignación y copia de los datos.
Por otro lado, OK, en la mayoría de los shells cat
no está integrado, lo que significa bifurcar un proceso y ejecutarlo (cargar el código y las bibliotecas), pero después de la primera invocación, ese código y el contenido del archivo de entrada será almacenado en la memoria caché. Por otro lado, no habrá intermediario. cat
leerá grandes cantidades a la vez y lo escribirá de inmediato sin procesar, y no necesita asignar una gran cantidad de memoria, solo ese búfer que reutiliza.
También significa que es mucho más confiable, ya que no se ahoga en bytes NUL y no recorta los caracteres de línea nueva (y no divide + glob, aunque puede evitarlo citando la variable, y no expanda la secuencia de escape, aunque puede evitarlo usando en printf
lugar de echo
).
Si desea optimizarlo aún más, en lugar de invocar cat
varias veces, simplemente pase input
varias veces a cat
.
yes input | head -n 100 | xargs cat
Ejecutará 3 comandos en lugar de 100.
Para hacer que la versión variable sea más confiable, necesitaría usar zsh
(otros shells no pueden hacer frente a bytes NUL) y hacerlo:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Si sabe que la entrada no contiene bytes NUL, entonces puede hacerlo de manera confiable POSIXY (aunque puede que no funcione donde printf
no está integrado) 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
Pero eso nunca será más eficiente que usarlo cat
en el bucle (a menos que la entrada sea muy pequeña).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)