echo vs <<<, ya da Yararsız Bash Ödülü?


19

Şimdiye kadar Yararsız catÖdül Kullanımı çok iyi biliniyor ve ayrıca Yararsız bir Kullanımdanecho da bahsediliyor ( bu soru için geçerli değil). "Bash'te Yararsız Kullanım echoÖdülü" olması gerekip gerekmediğini merak ediyorum : Borulama, son derece bilimsel olmayan bazı ölçümlere göre yorumlu metinlerden ve yorumlamalardan çok daha yavaş görünüyor:

  • Yorumlu metinler:

    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
    
  • Yönlendirme

    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

Genel olarak, yorumlu metinler ve yorumlamalar aynı hızdadır (bu, birkaç testten sadece bir veri kümesidir), yönlendirme sürekli olarak% 50'den daha yavaştır. Bir şeyi yanlış mı anlıyorum, yoksa bu Bash'deki standart girdiyi okuyan komutlar için genel bir kural olarak kullanılabilir mi?


4
Kesinlikle "yararsız kullanımı seq" ödülüne izin vereceğim ;-)
Chris Down

2
Ya karşılaştırmak gerektiğine Not cat <<ENDvs cat <<< "test string"vs echo "test string" | catveya cat <<'END'vs cat <<< 'test string'vs echo 'test string' | cataçılımları dizeleri üzerinde kabuk tarafından gerçekleştirilen aynı miktarda olması.
manatwork

@ChrisDown Derp. Bunu hep unuturum. Teşekkürler!
l0b0

@manatwork Teşekkürler, düzeltilmiş ve güncellenmiş zamanlamalar.
l0b0

Sadece bir merak, bu temsilcilerde ne var? Orijinal amacınız $ reps kez döngü ile değil miydi $(seq $reps)?
manatwork

Yanıtlar:


11

İlk olarak, performansa odaklanalım. Ben Debian sıkmak çalıştıran başka türlü çoğunlukla boş bir x86_64 işlemci üzerinde biraz farklı bir program için kriterler koştu.

herestring.bash, bir girdi satırını geçmek için bir test yöntemi kullanarak:

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

heredoc.bash, bir satır girdisi geçmek için bir yorumlu metin kullanma:

#! /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, echobir giriş satırını geçmek için ve bir boru kullanarak :

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

Karşılaştırma için, komut dosyalarını ATT ksh93 ve tire altında da zamanladım (hariç herestring.bash, tire herestring içermiyor).

İşte üç defa medyan:

$ 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

Sonuç:

  • Bir yorumlu metin yorumlama işleminden daha hızlıdır.
  • echove bir boru fark edilir, ancak çok daha hızlı değildir. (Bunun bir oyuncak programı olduğunu unutmayın: gerçek bir programda, işlem süresinin çoğu trburada çağrı ne anlama gelirse gelsin .)
  • Hız istiyorsanız, hendek bash ve çağrı çizgi veya daha iyi ksh. Bash'in özellikleri göreli yavaşlığını telafi etmez, ancak ksh'ın hem özellikleri hem de hızı vardır.

Performansın ötesinde, netlik ve taşınabilirlik de vardır. veya <<<daha az bilinen bir ksh93 / bash / zsh uzantısıdır . Ksh88 / pdksh veya POSIX sh'de çalışmaz.echo … |<<

<<<Tartışmasız önemli ölçüde daha net olan tek yer bir heredoc'un içinde:

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

vs

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

(Çoğu mermi içeren satırın sonunda parantezin kapatılmasıyla baş edemez <<EOF.)


2
Not <<<gelir rcve ilk tarafından Bourne benzeri kabuk dünyaya getirildi zsh. rcBir sondaki yeni satır eklemek, ama hepsi değil bash, zshve ksh93AFAICT yapmak. rcederken, orada bir boru kullanır bash, zshve ksh93heredoc için gibi geçici bir dosya kullanır.
Stéphane Chazelas

@StephaneChazelas Hata! Kontrol etmeliydim, teşekkürler. Bu <<<daha az kullanışlı hale getirir .
Gilles 'SO- kötü olmayı bırak'

Ayrıca etkin bir işlem bir çatal gibi içermez "foo" içeren bir geçici dosya oluşturmak için zshbir optimizasyon olduğunu unutmayın . =(<<<foo)=(echo foo)
Stéphane Chazelas

Ayrıca o pdksh gelmez söz edeceğiz değil bulunmaktadır <<<.
kurtm

@kurtm “ksh88 / pdksh veya POSIX sh'de çalışmaz.”
Gilles 'SO- kötü olmayı bırak'

6

Yorumlu metinleri kullanmanın başka bir nedeni de (yeterli değere sahip değilseniz), akışın tüketilmemesi durumunda yankının başarısız olabileceğidir. Bash ' pipefailseçeneğini kullanmayı düşünün :

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

/bin/truestandart girdisini tüketmez, ancak echo yawnyine de tamamlar. Ancak, yankıdan çok fazla veri yazdırması istenirse, tamamlanana kadar truetamamlanmaz:

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) 'dir (128 eklenir, çünkü bash bash'a göre yapar (1):

Bir komut ölümcül bir N sinyalinde sona erdiğinde, bash çıkış durumu olarak 128 + N değerini kullanır.

Yorumlu metinlerde bu sorun yoktur:

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

Teşekkürler, bu özel nüans betiğimin rastgele yerlerde, kvm ana bilgisayarlarından daha fazla Amazon sunucularında aralıklı olarak başarısız olmasına neden oluyordu, ancak zaman zaman başarısız olduğu imkansız yerlerde hala başarısız oldu. pipefailVarsayılan olarak kapalı olmak iyi bir şey !
mogsie

2
Oh, pipefailher senaryonun üst kısmında etkinleştirme . Bana tüm hataları ver!
l0b0

@ l0b0 Hmmm. Belki ben j.mp/safebash için pipefail ekleyeceğim Ben geliştirme sırasında tüm hataları almak için hepiniz!
Bruno Bronosky

2

Yankı kullanmak isteyebilmenizin bir nedeni, heredoc ve herestringlerin sonuna eklenen yeni satır karakterini biraz kontrol altına almaktır:

Üç karakterin foouzunluğu 3:

$ echo -n foo | wc -c
3

Ancak, üç karakterlik bir test, dört karakterdir:

$ wc -c <<< foo
4

Üç karakterli bir yorumlu metin:

$ wc -c << EOF
foo
EOF
4

Dördüncü karakter yeni satır 0x0akarakteridir.

Her nasılsa bu sihirli bir şekilde bash'ın bir alt kabuktan çıktı alırken bu yeni satır karakterlerini kaldırma biçimine uyuyor:

İşte dört karakter döndüren bir komut: foove \n. '\ N' echo tarafından eklenir, -nseçeneği belirtmedikçe her zaman yeni satır karakteri ekler :

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

Ancak, bunu bir değişkene atayarak, echo tarafından eklenen son satırsonu kaldırılır:

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

Dolayısıyla, dosyaları ve değişkenleri karıştırıp hesaplamalarda kullanırsanız (örneğin, bir yorumlu metin veya herestring kullanamazsınız, çünkü bunlar yeni satır ekleyecektir.

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

İf ifadesini okumak için değiştirirseniz

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

sonra test geçer.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.