Echo vs <<< oder unbrauchbare Verwendung von Echo in Bash Award?

18

Inzwischen ist die unbrauchbare Verwendung von catAwards sehr bekannt, und es wird auch eine unbrauchbare Verwendung vonecho (für diese Frage nicht relevant) erwähnt. Ich frage mich, ob es einen "Useless Use of echoin Bash Award" geben sollte: Piping scheint nach einigen hochgradig unwissenschaftlichen Messungen viel langsamer zu sein als Heredocs und Herestrings:

  • 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
  • Umleitung

    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

Im Allgemeinen haben Heredocs und Herestrings ungefähr dieselbe Geschwindigkeit (dies ist nur ein Datensatz aus mehreren Tests), während die Umleitung durchweg mehr als 50% langsamer ist. Verstehe ich etwas falsch oder kann dies als allgemeine Regel für Befehle zum Lesen von Standardeingaben in Bash verwendet werden?

l0b0
quelle
4
Ich erlaube dir auf jeden Fall eine "nutzlose Nutzung seq" der Auszeichnung ;-)
Chris Down
2
Beachten Sie, dass Sie entweder cat <<ENDvs. cat <<< "test string"vs. echo "test string" | catoder cat <<'END'vs. cat <<< 'test string'vs. vergleichen sollten echo 'test string' | cat, um die gleiche Anzahl von Erweiterungen zu erhalten, die von der Shell für die Zeichenfolgen ausgeführt werden.
Handarbeit
@ChrisDown Derp. Ich vergesse das immer. Vielen Dank!
20.
@manatwork Danke, feste und aktualisierte Zeiten.
l0b0
Nur eine Kuriosität, was ist mit diesen Wiederholungen? War es nicht deine ursprüngliche Absicht, $ reps-Zeiten zu wiederholen $(seq $reps)?
Handarbeit

Antworten:

11

Konzentrieren wir uns zunächst auf die Leistung. Ich habe Benchmarks für ein etwas anderes Programm auf einem ansonsten meist inaktiven x86_64-Prozessor mit Debian Squeeze ausgeführt.

herestring.bashVerwenden Sie einen Herestring, um eine Eingabezeile zu übergeben:

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

heredoc.bashVerwenden eines Heredocs zum Übergeben einer Eingabezeile:

#! /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.bashVerwenden Sie echound eine Pipe, um eine Eingabezeile zu übergeben:

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

Zum Vergleich habe ich auch die Skripte unter ATT ksh93 und unter dash zeitlich festgelegt (außer herestring.bash, weil dash keine Herestrings hat).

Hier ist der Median des Dreifachen:

$ 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

Schlussfolgerungen:

  • Ein Heredoc ist schneller als ein Herestring.
  • echound eine Pfeife ist merklich, aber nicht dramatisch schneller. (Denken Sie daran, dass dies ein Spielzeugprogramm ist: In einem echten Programm würde die meiste Verarbeitungszeit in dem liegen, wofür der trAufruf hier steht.)
  • Wenn Sie Geschwindigkeit wollen, lassen Sie es los und rufen Sie stattdessen dash oder noch besser ksh. Die Features von Bash machen die relative Langsamkeit nicht wett, aber ksh bietet sowohl Features als auch Geschwindigkeit.

Über die Leistung hinaus gibt es auch Klarheit und Portabilität. <<<ist eine ksh93 / bash / zsh-Erweiterung, die weniger bekannt ist als echo … |oder <<. Es funktioniert nicht in ksh88 / pdksh oder in POSIX sh.

Der einzige Ort, an dem <<<es wahrscheinlich viel klarer ist, befindet sich in einem Heredoc:

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

vs

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

(Die meisten Shells schaffen es nicht, die Klammer am Ende der Zeile zu schließen, die enthält <<EOF.)

Gilles 'SO - hör auf böse zu sein'
quelle
2
Beachten Sie, dass es <<<aus rcder Bourne-ähnlichen Shell-Welt stammt und zuerst von der Bourne-ähnlichen Shell-Welt gebracht wurde zsh. rchat keinen Newline, aber alle hinzufügen bash, zshund ksh93tun AFAICT. rcEs verwendet ein Rohr, während bash, zshund ksh93eine temporäre Datei wie für Heredocs verwenden.
Stéphane Chazelas
@StephaneChazelas Hoppla, ich hätte nachsehen sollen, danke. Dies macht <<<noch weniger nützlich.
Gilles 'SO- hör auf böse zu sein'
Beachten Sie auch, dass zshes eine Optimierung gibt, =(<<<foo)um effektiv eine temporäre Datei zu erstellen, die "foo" enthält, bei der ein Prozess nicht wie gewohnt gegabelt wird =(echo foo).
Stéphane Chazelas
Ich erwähne auch, dass pdksh nicht enthält <<<.
Kurtm
@kurtm „Es funktioniert nicht in ksh88 / pdksh oder in POSIX sh.“
Gilles 'SO- Anschlag, der böse'
6

Ein weiterer Grund für die Verwendung von Heredocs (wenn Sie nicht genug hatten) ist, dass das Echo fehlschlagen kann, wenn der Stream nicht verwendet wird. Erwägen Sie die bash- pipefailOption:

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

/bin/trueVerbraucht nicht die Standardeingabe, wird aber echo yawndennoch abgeschlossen. Wenn das Echo jedoch aufgefordert wird, viele Daten zu drucken, wird es erst abgeschlossen, wenn Folgendes trueabgeschlossen ist:

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 ist SIGPIPE (128 + 13) (128 wird hinzugefügt, weil bash dies gemäß bash (1) tut:

Wenn ein Befehl bei einem schwerwiegenden Signal N beendet wird, verwendet bash den Wert 128 + N als Beendigungsstatus.

Heredocs haben dieses Problem nicht:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always
Mogsie
quelle
Dank dieser besonderen Nuance scheiterte mein Skript zeitweise an zufälligen Orten, mehr auf Amazon-Servern als auf kvm-Hosts. Von Zeit zu Zeit scheiterte es jedoch an scheinbar unmöglich zu scheiternden Orten. Es ist eine gute Sache, dass die pipefailStandardeinstellung ausgeschaltet ist!
Mogsie
2
Oh, ich aktiviere pipefailam Anfang jedes Skripts. Gib mir alle Fehler!
15.
@ 10b0 Hmmm. Vielleicht füge ich Pipefail zu j.mp/safebash hinzu. Ich bin alle dafür, dass ich alle Fehler während der Entwicklung bekommen habe!
Bruno Bronosky
2

Ein Grund, warum Sie Echo verwenden möchten, besteht darin, das Zeilenumbruchzeichen, das an das Ende von heredocs und herestrings angehängt wird, zu steuern:

Drei Zeichen foohaben die Länge 3:

$ echo -n foo | wc -c
3

Ein Drei-Zeichen-Herestring besteht jedoch aus vier Zeichen:

$ wc -c <<< foo
4

Ein dreistelliger Heredoc:

$ wc -c << EOF
foo
EOF
4

Das vierte Zeichen ist ein Newline- 0x0aZeichen.

Irgendwie fügt sich das magisch in die Art ein, wie Bash diese Zeilenumbrüche entfernt, wenn Ausgaben aus einer Sub-Shell abgerufen werden:

Hier ist ein Befehl, der vier Zeichen zurückgibt: foound \n. Das '\ n' wird per Echo hinzugefügt. Es wird immer ein Zeilenumbruchzeichen hinzugefügt, es sei denn, Sie geben die -nOption an:

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

Wenn Sie dies jedoch einer Variablen zuweisen, wird die durch echo hinzugefügte nachgestellte Zeile entfernt:

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

Wenn Sie also Dateien und Variablen mischen und in Berechnungen verwenden (z. B. können Sie keine Heredocs oder Herestrings verwenden, da diese eine neue Zeile hinzufügen.

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

Wenn Sie die if-Anweisung in read ändern

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

dann ist der Test bestanden.

Mogsie
quelle