echo vs <<< o uso inutile di eco nel Bash Award?


19

Ormai l' uso inutile del catpremio è molto noto, e c'è anche una menzione di un uso inutile diecho (non pertinente per questa domanda). Mi chiedo se ci dovrebbe essere un "Uso inutile di echoin Bash Award": il piping sembra essere molto più lento di heredocs e herestring secondo alcune misurazioni altamente non scientifiche:

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

    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

In generale, heredocs e herestring hanno circa la stessa velocità (questo è solo un set di dati di diversi test) mentre il reindirizzamento è costantemente più lento di oltre il 50%. Sto fraintendendo qualcosa o potrebbe essere usato come regola generale per i comandi che leggono l'input standard in Bash?


4
Ti concederò certamente un seqpremio " uso inutile di " ;-)
Chris Down il

2
Si noti che è necessario confrontare cat <<ENDvs. cat <<< "test string"vs. echo "test string" | cato cat <<'END'vs. cat <<< 'test string'vs. echo 'test string' | catper avere la stessa quantità di espansioni eseguite dalla shell sulle stringhe.
arte

@ChrisDown Derp. Lo dimentico sempre. Grazie!
l0b0

@manatwork Grazie, orari fissi e aggiornati.
l0b0

Solo una curiosità, cos'è con quelle ripetizioni? La tua intenzione originale non era quella di ripetere i tempi di $ ripetizioni $(seq $reps)?
arte

Risposte:


11

Innanzitutto, concentriamoci sulle prestazioni. Ho eseguito benchmark per un programma leggermente diverso su un processore x86_64 altrimenti per lo più inattivo con Debian Squeeze.

herestring.bash, usando un herestring per passare una riga di input:

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

heredoc.bash, usando un heredoc per passare una riga di input:

#! /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, usando echoe una pipe per passare una linea di input:

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

Per fare un confronto, ho anche cronometrato gli script sotto ATT ksh93 e sotto trattino (tranne per herestring.bash, perché trattino non ha herestring).

Ecco una mediana delle tre volte:

$ 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

conclusioni:

  • Un heredoc è più veloce di un herestring.
  • echoe una pipa è notevolmente, ma non drammaticamente più veloce. (Tieni presente che questo è un programma giocattolo: in un programma reale, la maggior parte del tempo di elaborazione sarebbe in qualunque cosa la trchiamata rappresenti qui.)
  • Se vuoi la velocità, abbandona bash e chiama dash o ancora meglio ksh. Le funzionalità di Bash non compensano la relativa lentezza, ma ksh ha sia funzionalità che velocità.

Oltre alle prestazioni, c'è anche chiarezza e portabilità. <<<è un'estensione ksh93 / bash / zsh che è meno nota di echo … |o <<. Non funziona in ksh88 / pdksh o in POSIX sh.

L'unico posto in cui <<<è discutibilmente significativamente più chiaro è all'interno di una eredità:

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

vs

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

(La maggior parte delle shell non riesce a far fronte alla chiusura della parentesi alla fine della riga contenente <<EOF.)


2
Si noti che <<<proviene da rc, ed è stato portato per la prima volta nel mondo shell simile a Bourne da zsh. rcnon aggiungere una nuova riga finale, ma tutti bash, zshe ksh93fare AFAICT. rcusa una pipe lì, mentre bash, zshe ksh93usa un file temporaneo come per heredocs.
Stéphane Chazelas,

@StephaneChazelas Oops, avrei dovuto controllare, grazie. Questo rende <<<ancora meno utile.
Gilles 'SO- smetti di essere malvagio' il

Si noti inoltre che zshha un'ottimizzazione =(<<<foo)per creare in modo efficace un file temporaneo che contiene "pippo" che non comporta il fork di un processo come =(echo foo)farebbe.
Stéphane Chazelas,

Citerò anche che pdksh non include <<<.
Kurtm,

@kurtm "Non funziona in ksh88 / pdksh o in POSIX sh."
Gilles "SO- smetti di essere malvagio"

6

Un altro motivo per usare heredocs (se non ne hai abbastanza) è che l'eco può fallire se il flusso non viene consumato. Prendi in considerazione l' pipefailopzione bash :

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

/bin/truenon consuma il suo input standard, ma echo yawncompleta comunque. Tuttavia, se viene richiesto a echo di stampare molti dati, non verrà completato fino a quando non sarà truecompletato:

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 è SIGPIPE (128 + 13) (128 aggiunto perché bash lo fa secondo bash (1):

Quando un comando termina su un segnale fatale N, bash utilizza il valore di 128 + N come stato di uscita.

Heredocs non ha questo problema:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always

Grazie, questa particolare sfumatura stava causando il fallimento intermittente del mio script in luoghi casuali, più sui server Amazon che sugli host kvm, ma di tanto in tanto falliva, in luoghi apparentemente impossibili da fallire. È una buona cosa che l' pipefailimpostazione predefinita sia disattivata!
mogsie,

2
Oh, abilito pipefailnella parte superiore di ogni sceneggiatura. Dammi tutti gli errori!
l0b0

@ l0b0 Hmmm. Forse aggiungerò pipefail a j.mp/safebash Sono tutto per ottenere tutti gli errori durante lo sviluppo!
Bruno Bronosky,

2

Uno dei motivi per cui potresti voler usare l'eco è di esercitare un certo controllo sul carattere newline che viene aggiunto alla fine di heredocs e herestrings:

Tre caratteri fooha lunghezza 3:

$ echo -n foo | wc -c
3

Tuttavia, un personaggio da tre personaggi è composto da quattro personaggi:

$ wc -c <<< foo
4

Anche un'eredità di tre personaggi:

$ wc -c << EOF
foo
EOF
4

Il quarto personaggio è un 0x0apersonaggio newline .

In qualche modo questo si adatta magicamente al modo in cui bash rimuove questi caratteri di nuova riga quando si prende l'output da una sotto-shell:

Ecco un comando che restituisce quattro caratteri: fooe \n. '\ N' viene aggiunto da eco, aggiunge sempre un carattere di nuova riga a meno che non specifichi l' -nopzione:

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

Tuttavia, assegnando questo a una variabile, la nuova riga finale aggiunta da echo viene rimossa:

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

Quindi, se mescoli file e variabili e li usi nei calcoli (ad esempio, non puoi utilizzare heredocs o herestrings, poiché aggiungeranno una nuova riga.

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

Se si modifica l'istruzione if in lettura

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

quindi il test ha esito positivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.