Il y a plusieurs choses à considérer ici.
i=`cat input`
peut être cher et il y a beaucoup de variations entre les coques.
C'est une fonctionnalité appelée substitution de commandes. L'idée est de stocker la sortie entière de la commande moins les caractères de fin de ligne dans la i
variable en mémoire.
Pour ce faire, les shells forkent la commande dans un sous-shell et lisent sa sortie via un tube ou une paire de sockets. Vous voyez beaucoup de variations ici. Sur un fichier de 50 Mo ici, je peux voir par exemple que bash est 6 fois plus lent que ksh93 mais légèrement plus rapide que zsh et deux fois plus rapide que yash
.
La principale raison d' bash
être lent est qu'il lit à partir du canal 128 octets à la fois (tandis que d'autres shells lisent 4KiB ou 8KiB à la fois) et est pénalisé par la surcharge des appels système.
zsh
doit effectuer un post-traitement pour échapper aux octets NUL (d'autres shells se brisent sur les octets NUL), et yash
effectue un traitement encore plus intensif en analysant les caractères multi-octets.
Tous les shells doivent supprimer les caractères de fin de ligne qu'ils peuvent faire plus ou moins efficacement.
Certains peuvent vouloir gérer les octets NUL plus gracieusement que d'autres et vérifier leur présence.
Ensuite, une fois que vous avez cette grande variable en mémoire, toute manipulation implique généralement d'allouer plus de mémoire et de copier les données.
Ici, vous passez (vouliez passer) le contenu de la variable à echo
.
Heureusement, echo
est intégré dans votre shell, sinon l'exécution aurait probablement échoué avec une erreur de liste d'arguments trop longue . Même dans ce cas, la construction du tableau de la liste des arguments impliquera éventuellement la copie du contenu de la variable.
L'autre problème principal dans votre approche de substitution de commandes est que vous invoquez l' opérateur split + glob (en oubliant de citer la variable).
Pour cela, les shells doivent traiter la chaîne comme une chaîne de caractères (bien que certains shells ne le soient pas et soient bogués à cet égard), donc dans les environnements locaux UTF-8, cela signifie analyser les séquences UTF-8 (si ce n'est déjà fait comme le yash
fait le cas) , recherchez les $IFS
caractères dans la chaîne. S'il $IFS
contient de l'espace, une tabulation ou une nouvelle ligne (ce qui est le cas par défaut), l'algorithme est encore plus complexe et coûteux. Ensuite, les mots résultant de ce fractionnement doivent être alloués et copiés.
La partie glob sera encore plus chère. Si l' un de ces mots contiennent des caractères glob ( *
, ?
, [
), le shell devra lire le contenu de certains répertoires et faire un peu de correspondance de motif coûteux ( bash
la mise en œuvre « , par exemple , est notoirement très mal à cela).
Si l'entrée contient quelque chose comme /*/*/*/../../../*/*/*/../../../*/*/*
ça, cela coûtera extrêmement cher car cela signifie répertorier des milliers d'annuaires et cela peut s'étendre à plusieurs centaines de Mio.
Ensuite, echo
il effectuera généralement un traitement supplémentaire. Certaines implémentations développent des \x
séquences dans l'argument qu'il reçoit, ce qui signifie analyser le contenu et probablement une autre allocation et copie des données.
D'un autre côté, OK, dans la plupart des shells cat
n'est pas intégré, ce qui signifie bifurquer un processus et l'exécuter (donc charger le code et les bibliothèques), mais après la première invocation, ce code et le contenu du fichier d'entrée sera mis en cache en mémoire. En revanche, il n'y aura pas d'intermédiaire. cat
lira de grandes quantités à la fois et l'écrira immédiatement sans traitement, et il n'a pas besoin d'allouer une énorme quantité de mémoire, juste ce tampon qu'il réutilise.
Cela signifie également qu'il est beaucoup plus fiable car il ne s'étouffe pas sur les octets NUL et ne supprime pas les caractères de fin de ligne (et ne fait pas de split + glob, bien que vous puissiez éviter cela en citant la variable et ne pas développez la séquence d'échappement bien que vous puissiez éviter cela en utilisant printf
au lieu de echo
).
Si vous souhaitez l'optimiser davantage, au lieu d'appeler cat
plusieurs fois, passez simplement input
plusieurs fois à cat
.
yes input | head -n 100 | xargs cat
Exécute 3 commandes au lieu de 100.
Pour rendre la version variable plus fiable, vous devez utiliser zsh
(les autres shells ne peuvent pas gérer les octets NUL) et le faire:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Si vous savez que l'entrée ne contient pas d'octets NUL, vous pouvez le faire de manière fiable POSIXly (bien que cela puisse ne pas fonctionner là où il printf
n'est pas intégré) avec:
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
Mais cela ne sera jamais plus efficace que l'utilisation cat
dans la boucle (sauf si l'entrée est très petite).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)