gema vs <<<, atau Penggunaan Gema yang Tidak Berguna di Bash Award?

19

Pada saat Penggunaan Useless of catAward sangat terkenal, dan ada juga yang menyebutkan Penggunaan Useless ofecho (tidak relevan untuk pertanyaan ini). Saya bertanya-tanya apakah harus ada "Penggunaan yang Tidak Berguna echodalam Bash Award": Perpipaan tampaknya jauh lebih lambat daripada heredocs dan herestrings menurut beberapa pengukuran yang sangat tidak ilmiah:

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

    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
    

Secara umum, heredocs dan herestrings memiliki kecepatan yang sama (ini hanya satu set data dari beberapa tes) sementara pengalihan secara konsisten lebih dari 50% lebih lambat. Apakah saya salah memahami sesuatu, atau dapatkah ini digunakan sebagai aturan umum untuk perintah yang membaca input standar di Bash?

l0b0
sumber
4
Saya pasti akan memberi Anda seqpenghargaan " penggunaan yang tidak berguna " ;-)
Chris Down
2
Perhatikan bahwa Anda harus membandingkan cat <<ENDvs cat <<< "test string"vs echo "test string" | catatau cat <<'END'vs cat <<< 'test string'vs echo 'test string' | catuntuk memiliki jumlah ekspansi yang sama yang dilakukan oleh shell pada string.
manatwork
@ChrisDown Derp. Saya selalu lupa itu. Terima kasih!
l0b0
@manatwork Terima kasih, perbaikan waktu dan pembaruan.
l0b0
Hanya rasa ingin tahu, ada apa dengan perwakilan itu? Bukankah niat awal Anda untuk mengulang $ reps kali $(seq $reps)?
manatwork

Jawaban:

11

Pertama, mari kita berkonsentrasi pada kinerja. Saya menjalankan tolok ukur untuk program yang sedikit berbeda pada prosesor x86_64 yang sebagian besar tidak bekerja menjalankan pemerasan Debian.

herestring.bash, menggunakan herestring untuk memberikan masukan:

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

heredoc.bash, menggunakan heredoc untuk memberikan masukan:

#! /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, menggunakan echodan pipa untuk melewati jalur input:

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

Sebagai perbandingan, saya juga mengatur waktu skrip di bawah ATT ksh93 dan di bawah tanda hubung (kecuali untuk herestring.bash, karena tanda hubung tidak memiliki herestrings).

Inilah median tiga kali:

$ 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

Kesimpulan:

  • A heredoc lebih cepat dari herestring.
  • echodan sebuah pipa terasa, tetapi tidak secara dramatis lebih cepat. (Ingatlah bahwa ini adalah program mainan: dalam program nyata, sebagian besar waktu pemrosesan akan sesuai dengan apa pun trpanggilannya di sini.)
  • Jika Anda ingin kecepatan, parit bash dan panggil dash atau bahkan lebih baik ksh. Fitur Bash tidak menggantikan kelambatan relatifnya, tetapi ksh memiliki kedua fitur dan kecepatan.

Selain kinerja, ada juga kejelasan dan portabilitas. <<<adalah ekstensi ksh93 / bash / zsh yang kurang dikenal echo … |atau <<. Ini tidak berfungsi di ksh88 / pdksh atau di POSIX sh.

Satu-satunya tempat di mana <<<secara signifikan lebih jelas adalah di dalam heredoc:

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

vs.

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

(Kebanyakan shell tidak dapat mengatasi penutupan tanda kurung di akhir baris yang berisi <<EOF.)

Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
Catatan yang <<<berasal dari rc, dan pertama kali dibawa ke dunia shell seperti Bourne oleh zsh. rctidak menambahkan baris baru, tetapi semuanya bash, zshdan ksh93lakukan AFAICT. rcmenggunakan pipa di sana, sementara bash, zshdan ksh93menggunakan file sementara seperti untuk heredocs.
Stéphane Chazelas
@StephaneChazelas Ups, saya harus memeriksa, terima kasih. Ini membuat <<<bahkan kurang bermanfaat.
Gilles 'SANGAT berhenti menjadi jahat'
Perhatikan juga bahwa zshmemiliki pengoptimalan =(<<<foo)untuk secara efektif membuat file temp yang berisi "foo" yang tidak melibatkan proses forking seperti yang =(echo foo)akan terjadi.
Stéphane Chazelas
Saya juga akan menyebutkan bahwa pdksh tidak termasuk <<<.
kurtm
@kurtm “Itu tidak bekerja di ksh88 / pdksh atau di POSIX sh.”
Gilles 'SO- stop being evil'
6

Alasan lain untuk menggunakan heredocs (jika Anda tidak memiliki cukup) adalah bahwa gema dapat gagal jika aliran tidak dikonsumsi. Pertimbangkan memiliki pipefailopsi bash ' :

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

/bin/truetidak mengkonsumsi input standarnya, tetapi echo yawntetap melengkapi. Namun, jika gema diminta untuk mencetak banyak data, itu tidak akan selesai sampai setelah trueselesai:

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 adalah SIGPIPE (128 + 13) (128 ditambahkan karena bash melakukannya sesuai dengan bash (1):

Ketika sebuah perintah berakhir pada sinyal fatal N, bash menggunakan nilai 128 + N sebagai status keluar.

Heredocs tidak memiliki masalah ini:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always
mogsie
sumber
Terima kasih, nuansa khusus ini menyebabkan skrip saya gagal sebentar-sebentar di tempat-tempat acak, lebih pada server Amazon daripada pada host kvm, tetapi masih gagal dari waktu ke waktu, di tempat-tempat yang tampaknya mustahil-gagal. Adalah hal yang baik jika pipefaildefaultnya tidak aktif!
mogsie
2
Oh, saya aktifkan pipefaildi bagian atas setiap skrip. Beri aku semua kesalahan!
l0b0
@ l0b0 Hmmm. Mungkin saya akan menambahkan pipefail ke j.mp/safebash Saya semua untuk mendapatkan semua kesalahan selama pengembangan!
Bruno Bronosky
2

Salah satu alasan Anda mungkin ingin menggunakan gema adalah untuk mengontrol beberapa karakter baris baru yang ditambahkan ke akhir heredocs dan herestrings:

Tiga karakter foomemiliki panjang 3:

$ echo -n foo | wc -c
3

Namun, threecharacter herestring adalah empat karakter:

$ wc -c <<< foo
4

Heredoc tiga karakter juga:

$ wc -c << EOF
foo
EOF
4

Karakter keempat adalah 0x0akarakter baris baru .

Entah bagaimana ini secara ajaib cocok dengan cara bash menghapus karakter-karakter baris baru ini ketika mengambil output dari sub-shell:

Berikut adalah perintah yang mengembalikan empat karakter: foodan \n. '\ N' ditambahkan oleh gema, itu selalu menambahkan karakter baris baru kecuali jika Anda menentukan -nopsi:

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

Namun, dengan menetapkan ini ke variabel, baris baru yang ditambahkan oleh gema dihapus:

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

Jadi jika Anda mencampur file dan variabel dan menggunakannya dalam perhitungan (misalnya, Anda tidak dapat menggunakan heredocs atau herestrings, karena mereka akan menambahkan baris baru.

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

Jika Anda mengubah pernyataan if untuk dibaca

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

kemudian tes berlalu.

mogsie
sumber