eco vs <<<, ou uso inútil de eco no Bash Award?

19

Até agora, o Uso inútil do catprêmio é muito conhecido, e também há uma menção ao Uso inútil deecho (não relevante para esta pergunta). Gostaria de saber se deve haver um "Uso Inútil echono Prêmio Bash": a tubulação parece ser muito mais lenta que os heredocs e herestrings, de acordo com algumas medidas altamente não científicas:

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

    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

Em geral, heredocs e herestrings têm aproximadamente a mesma velocidade (este é apenas um conjunto de dados de vários testes) enquanto o redirecionamento é consistentemente mais do que 50% mais lento. Estou entendendo mal algo ou isso poderia ser usado como regra geral para comandos que leem a entrada padrão no Bash?

l0b0
fonte
4
Eu vou certamente permitir-lhe uma "utilização inútil de seq" prêmio ;-)
Chris Baixo
2
Observe que você deve comparar cat <<ENDvs. cat <<< "test string"vs. echo "test string" | catou cat <<'END'vs. cat <<< 'test string'vs. echo 'test string' | catpara ter a mesma quantidade de expansões executadas pelo shell nas seqüências.
manatwork
@ChrisDown Derp. Eu sempre esqueço esse. Obrigado!
l0b0
@manatwork Obrigado, horários fixos e atualizados.
l0b0
Apenas uma curiosidade, o que há com esses representantes? A sua intenção original não era repetir os tempos de repetições $(seq $reps)?
manatwork

Respostas:

11

Primeiro, vamos nos concentrar no desempenho. Eu executei benchmarks para um programa um pouco diferente em um processador x86_64 normalmente ocioso executando o Debian squeeze.

herestring.bash, usando um herestring para passar uma linha de entrada:

#! /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 um heredoc para passar uma linha de entrada:

#! /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, using echoe um pipe para passar uma linha de entrada:

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

Para comparação, também cronometrei os scripts no ATT ksh93 e no dash (exceto herestring.bashporque o dash não possui legendas).

Aqui estão a mediana de três vezes:

$ 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

Conclusões:

  • Um heredoc é mais rápido que um herestring.
  • echoe um tubo é visivelmente, mas não dramaticamente mais rápido. (Lembre-se de que este é um programa de brinquedos: em um programa real, a maior parte do tempo de processamento seria em qualquer que seja a trchamada aqui.)
  • Se você deseja velocidade, descarte o bash e chame o dash ou, melhor ainda, o ksh. Os recursos do Bash não compensam sua relativa lentidão, mas o ksh possui recursos e velocidade.

Além do desempenho, também há clareza e portabilidade. <<<é uma extensão ksh93 / bash / zsh que é menos conhecida que echo … |ou <<. Não funciona no ksh88 / pdksh ou no POSIX sh.

O único lugar onde <<<é discutivelmente significativamente mais claro é dentro de um heredoc:

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

vs

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

(A maioria das conchas não consegue lidar com o fechamento dos parênteses no final da linha que contém <<EOF.)

Gilles 'SO- parar de ser mau'
fonte
2
Note que <<<vem rce foi trazido pela primeira vez ao mundo de conchas tipo Bourne zsh. rcnão adicionou uma nova linha à direita, mas todas bash, zshe ksh93faz AFAICT. rcusa um pipe lá, while bash, zshe ksh93usa um arquivo temporário como para heredocs.
Stéphane Chazelas
@StephaneChazelas Opa, eu deveria ter verificado, obrigado. Isso torna <<<ainda menos útil.
Gilles 'SO- stop be evil'
Observe também que zshhá uma otimização =(<<<foo)para criar efetivamente um arquivo temporário que contém "foo", que não envolve a bifurcação de um processo como =(echo foo)faria.
Stéphane Chazelas
Mencionarei também que o pdksh não inclui <<<.
kurtm
@kurtm “Não funciona no ksh88 / pdksh ou no POSIX sh.”
Gilles 'SO- deixa de ser mau'
6

Outro motivo para usar heredocs (se você não tiver o suficiente) é que o eco poderá falhar se o fluxo não for consumido. Considere ter a pipefailopção bash ' :

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

/bin/truenão consome sua entrada padrão, mas é echo yawnconcluído mesmo assim. No entanto, se for solicitado que o eco imprima muitos dados, ele não será concluído até que trueseja concluído:

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 sendo adicionado porque o bash faz isso de acordo com o bash (1):

Quando um comando termina em um sinal fatal N, o bash usa o valor de 128 + N como status de saída.

Heredocs não têm esse problema:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always
mogsie
fonte
Obrigado, essa nuance específica estava causando falha no meu script de forma intermitente em locais aleatórios, mais nos servidores Amazon do que nos hosts kvm, mas ainda falhava de tempos em tempos, em locais aparentemente impossíveis de falhar. É bom que o pipefailpadrão seja desligado!
mogsie
2
Ah, eu ativo pipefailno topo de cada script. Me dê todos os erros!
L0b0 15/10
@ l0b0 Hmmm. Talvez eu adicione pipefail ao j.mp/safebash . Sou a favor de obter todos os erros durante o desenvolvimento!
precisa saber é o seguinte
2

Um motivo pelo qual você pode querer usar echo é exercer algum controle sobre o caractere de nova linha que é adicionado ao final de heredocs e herestrings:

Três caracteres footem comprimento 3:

$ echo -n foo | wc -c
3

No entanto, um caractere de três caracteres possui quatro caracteres:

$ wc -c <<< foo
4

Um heredoc de três caracteres também:

$ wc -c << EOF
foo
EOF
4

O quarto caractere é um 0x0acaractere de nova linha .

De alguma forma, isso se encaixa magicamente com a maneira como o bash remove esses caracteres de nova linha ao obter a saída de um sub-shell:

Aqui está um comando que retorna quatro caracteres: fooe \n. O '\ n' é adicionado pelo eco, ele sempre adiciona um caractere de nova linha, a menos que você especifique a -nopção:

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

No entanto, atribuindo isso a uma variável, a nova linha à direita adicionada pelo eco é removida:

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

Portanto, se você combinar arquivos e variáveis ​​e usá-los em cálculos (por exemplo, não poderá usar heredocs ou herestrings, pois eles adicionarão uma nova linha.

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 você alterar a instrução if para ler

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

então o teste passa.

mogsie
fonte