écho vs <<<, ou utilisation inutile d'écho dans Bash Award?

19

À présent, l' utilisation inutile du catprix est très bien connue, et il est également fait mention d'une utilisation inutile deecho (non pertinente pour cette question). Je me demande s'il devrait y avoir une "utilisation inutile de echoin Bash Award": la tuyauterie semble être beaucoup plus lente que les heredocs et les herestrings selon certaines mesures très non scientifiques:

  • Heredocs:

    for reps in 1 2 3
    do
        time for i in {1..1000}
        do
            cat <<'END'
    test string
    END
        done > /dev/null
    done
    
    real    0m1.786s
    user    0m0.212s
    sys     0m0.332s
    
    real    0m1.817s
    user    0m0.232s
    sys     0m0.332s
    
    real    0m1.846s
    user    0m0.256s
    sys     0m0.320s
    
  • Herestrings

    for reps in 1 2 3
    do
        time for i in {1..1000}
        do
            cat <<< 'test string'
        done > /dev/null
    done
    
    real    0m1.932s
    user    0m0.280s
    sys     0m0.288s
    
    real    0m1.956s
    user    0m0.248s
    sys     0m0.348s
    
    real    0m1.968s
    user    0m0.268s
    sys     0m0.324s
    
  • Redirection

    for reps in 1 2 3
    do
        time for i in {1..1000}
        do
            echo 'test string' | cat
        done > /dev/null
    done
    
    real    0m3.562s
    user    0m0.416s
    sys     0m0.548s
    
    real    0m3.924s
    user    0m0.384s
    sys     0m0.604s
    
    real    0m3.343s
    user    0m0.400s
    sys     0m0.552s
    

En général, les heredocs et les herestrings sont à peu près à la même vitesse (ce n'est qu'un ensemble de données provenant de plusieurs tests) tandis que la redirection est systématiquement plus de 50% plus lente. Suis-je en train de mal comprendre quelque chose, ou cela pourrait-il être utilisé comme règle générale pour les commandes lisant l'entrée standard dans Bash?

l0b0
la source
4
Je vais certainement vous permettre une seqrécompense pour " utilisation inutile de " ;-)
Chris Down
2
Notez que vous devez comparer cat <<ENDvs cat <<< "test string"vs echo "test string" | catou cat <<'END'vs cat <<< 'test string'vs echo 'test string' | catpour avoir la même quantité d'extensions effectuées par le shell sur les chaînes.
manatwork
@ChrisDown Derp. J'oublie toujours celui-là. Merci!
l0b0
@manatwork Merci, horaires fixes et mis à jour.
l0b0
Juste une curiosité, qu'est-ce que c'est que ces représentants? Votre intention initiale n'était-elle pas de boucler $ reps avec $(seq $reps)?
manatwork

Réponses:

11

Tout d'abord, concentrons-nous sur les performances. J'ai exécuté des tests de performance pour un programme légèrement différent sur un processeur x86_64 par ailleurs essentiellement inactif exécutant Debian Squeeze.

herestring.bash, en utilisant une chaîne héritée pour passer une ligne d'entrée:

#! /bin/bash
i=0
while [ $i -lt $1 ]; do
  tr a-z A-Z <<<'hello world'
  i=$((i+1))
done >/dev/null

heredoc.bash, en utilisant un hérédoc pour passer une ligne d'entrée:

#! /bin/bash
i=0
while [ $i -lt $1 ]; do
  tr a-z A-Z <<'EOF'
hello world
EOF
  i=$((i+1))
done >/dev/null

echo.bash, en utilisant echoet un tuyau pour passer une ligne d'entrée:

#! /bin/bash
i=0
while [ $i -lt $1 ]; do
  echo 'hello world' | tr a-z A-Z
  i=$((i+1))
done >/dev/null

À titre de comparaison, j'ai également chronométré les scripts sous ATT ksh93 et ​​sous tiret (sauf pour herestring.bash, car le tiret n'a pas ses chaînes).

Voici la médiane de trois fois:

$ time bash ./herestring.bash 10000
./herestring.bash 10000  0.32s user 0.79s system 15% cpu 7.088 total
$ time ksh ./herestring.bash 10000
ksh ./herestring.bash 10000  0.54s user 0.41s system 17% cpu 5.277 total
$ time bash ./heredoc.bash 10000
./heredoc.bash 10000  0.35s user 0.75s system 17% cpu 6.406 total
$ time ksh ./heredoc.bash 10000  
ksh ./heredoc.sh 10000  0.54s user 0.44s system 19% cpu 4.925 total
$ time sh ./heredoc.bash 10000  
./heredoc.sh 10000  0.08s user 0.58s system 12% cpu 5.313 total
$ time bash ./echo.bash 10000
./echo.bash 10000  0.36s user 1.40s system 20% cpu 8.641 total
$ time ksh ./echo.bash 10000
ksh ./echo.sh 10000  0.47s user 1.51s system 28% cpu 6.918 total
$ time sh ./echo.sh 10000
./echo.sh 10000  0.07s user 1.00s system 16% cpu 6.463 total

Conclusions:

  • Un hérédoc est plus rapide qu'un héritage.
  • echoet une pipe est sensiblement, mais pas beaucoup plus rapide. (Gardez à l'esprit qu'il s'agit d'un programme jouet: dans un programme réel, la majeure partie du temps de traitement serait dans tout ce que l' trappel représente ici.)
  • Si vous voulez de la vitesse, abandonnez bash et appelez dash ou encore mieux ksh à la place. Les fonctionnalités de Bash ne compensent pas sa lenteur relative, mais ksh a à la fois des fonctionnalités et de la vitesse.

Au-delà des performances, il y a aussi la clarté et la portabilité. <<<est une extension ksh93 / bash / zsh qui est moins connue que echo … |ou <<. Cela ne fonctionne pas dans ksh88 / pdksh ou dans POSIX sh.

Le seul endroit où il <<<est sans doute nettement plus clair est à l'intérieur d'un hérédoc:

foo=$(tr a-z A-Z <<<'hello world')

contre

foo=$(tr a-z A-Z <<'EOF'
hello world
EOF
)

(La plupart des shells ne peuvent pas faire face à la fermeture de la parenthèse à la fin de la ligne contenant <<EOF.)

Gilles 'SO- arrête d'être méchant'
la source
2
Notez que cela <<<vient de rc, et a été introduit pour la première fois dans le monde de la coque Bourne zsh. rcn'a pas ajouté de nouvelle ligne de fin, mais tous bash, zshet ksh93font AFAICT. rcutilise un canal là-bas, tandis que bash, zshet ksh93utilisez un fichier temporaire comme pour les heredocs.
Stéphane Chazelas
@StephaneChazelas Oups, j'aurais dû vérifier, merci. Cela rend <<<encore moins utile.
Gilles 'SO- arrête d'être méchant'
Notez également qu'il zsha une optimisation =(<<<foo)pour créer efficacement un fichier temporaire qui contient "foo" qui n'implique pas de bifurquer un processus comme le =(echo foo)ferait.
Stéphane Chazelas
Je mentionnerai également que pdksh ne comprend pas<<< .
kurtm
@kurtm "Ça ne marche pas dans ksh88 / pdksh ou dans POSIX sh."
Gilles 'SO- arrête d'être méchant'
6

Une autre raison d'utiliser heredocs (si vous n'en avez pas assez) est que l'écho peut échouer si le flux n'est pas consommé. Pensez à avoir l' pipefailoption bash ' :

set -o pipefail
foo=yawn
echo $foo | /bin/true ; echo $?  # returns 0

/bin/truene consomme pas son entrée standard, mais se echo yawntermine néanmoins. Cependant, si l'écho est invité à imprimer un grand nombre de données, il ne se terminera qu'après la truefin:

foo=$(cat /etc/passwd)
# foo now has a fair amount of data

echo $foo | /bin/true ; echo $?  # returns 0 sometimes 141
echo $foo$foo$foo$foo | /bin/true ; echo $?  # returns mostly 141

141 est SIGPIPE (128 + 13) (128 étant ajouté car bash le fait selon bash (1):

Lorsqu'une commande se termine sur un signal fatal N, bash utilise la valeur de 128 + N comme état de sortie.

Heredocs n'a pas ce problème:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always
mogsie
la source
Merci, cette nuance particulière faisait échouer mon script par intermittence à des endroits aléatoires, plus sur les serveurs Amazon que sur les hôtes kvm, mais il échouait de temps en temps, dans des endroits apparemment impossibles à échouer. C'est une bonne chose que les pipefailvaleurs par défaut soient éteintes!
mogsie
2
Oh, j'active pipefailen haut de chaque script. Donne-moi toutes les erreurs!
l0b0
@ l0b0 Hmmm. J'ajouterai peut-être pipefail à j.mp/safebash Je suis tout à fait d' accord pour obtenir toutes les erreurs pendant le développement!
Bruno Bronosky
2

Une raison pour laquelle vous voudrez peut-être utiliser l'écho est d'exercer un certain contrôle sur le caractère de nouvelle ligne qui est ajouté à la fin des heredocs et herestrings:

Trois caractères fooont une longueur de 3:

$ echo -n foo | wc -c
3

Cependant, une chaîne de caractères à trois caractères comprend quatre caractères:

$ wc -c <<< foo
4

Un hérédoc à trois caractères aussi:

$ wc -c << EOF
foo
EOF
4

Le quatrième caractère est un 0x0acaractère de nouvelle ligne .

D'une manière ou d'une autre, cela correspond magiquement à la façon dont bash supprime ces caractères de nouvelle ligne lors de la récupération de la sortie d'un sous-shell:

Voici une commande qui renvoie quatre caractères: fooet \n. Le '\ n' est ajouté par echo, il ajoute toujours un caractère de nouvelle ligne sauf si vous spécifiez l' -noption:

$ echo foo
foo
$ echo foo | wc -c
4

Cependant, en l'affectant à une variable, la nouvelle ligne de fin ajoutée par echo est supprimée:

$ foo=$(echo foo)
$ echo "$foo" # here, echo adds a newline too.
foo

Donc, si vous mélangez des fichiers et des variables et les utilisez dans des calculs (par exemple, vous ne pouvez pas utiliser des heredocs ou des herestrings, car ils ajouteront une nouvelle ligne.

foo=abc
echo -n 'abc' > something.txt
if [ $(wc -c <<< "$foo") -eq $(wc -c < something.txt) ] ; then
  echo "yeah, they're the same!"
else
  echo "foo and bar have different lengths (except, maybe not)"
fi

Si vous modifiez l'instruction if pour lire

if [ $(echo -n "$foo" | wc -c) -eq $(wc -c < something.txt) ] ; then

puis le test réussit.

mogsie
la source