echo vs <<< หรือใช้ echo ใน Bash Award อย่างไร้ประโยชน์?


19

โดยตอนนี้ใช้ประโยชน์ของcatรางวัลเป็นที่รู้จักกันเป็นอย่างดีและยังมีการเอ่ยถึงการใช้ประโยชน์ของecho (ไม่เกี่ยวข้องกับคำถามนี้) ฉันสงสัยว่าควรจะมี "การใช้งานไร้ประโยชน์echoในรางวัล Bash": การวางท่อดูเหมือนจะช้ากว่า heredocs และ 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
  • การเปลี่ยนเส้นทาง

    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

โดยทั่วไป heredocs และ herestrings นั้นมีความเร็วเท่ากัน (นี่เป็นเพียงชุดข้อมูลเดียวจากการทดสอบหลายชุด) ในขณะที่การเปลี่ยนเส้นทางช้ากว่า 50% อย่างต่อเนื่อง ฉันเข้าใจผิดบางอย่างหรืออาจใช้เป็นกฎทั่วไปสำหรับคำสั่งที่อ่านอินพุตมาตรฐานใน Bash


4
แน่นอนฉันจะอนุญาตให้คุณใช้ "ประโยชน์ใช้seq" ;-)
Chris Down

2
โปรดทราบว่าคุณควรเปรียบเทียบcat <<ENDกับcat <<< "test string"และecho "test string" | catหรือcat <<'END'เทียบcat <<< 'test string'กับecho 'test string' | catเพื่อให้มีการขยายจำนวนเท่ากันโดยเชลล์ในสตริง
จัดการ

@ChrisDown Derp ฉันลืมสิ่งนั้นเสมอ ขอบคุณ!
l0b0

@ การจัดการขอบคุณกำหนดเวลาที่ถูกแก้ไขและปรับปรุงแล้ว
l0b0

แค่อยากรู้อยากเห็นอะไรคือสิ่งที่พนักงานที่? ความตั้งใจดั้งเดิมของคุณในการวนครั้ง $ reps $(seq $reps)ไม่ใช่หรือ?
จัดการ

คำตอบ:


11

ก่อนอื่นมาเน้นประสิทธิภาพกันก่อน ฉันใช้การวัดประสิทธิภาพสำหรับโปรแกรมที่แตกต่างกันเล็กน้อยในโปรเซสเซอร์ x86_64 ที่ไม่ทำงานส่วนใหญ่ที่รัน Debian squeeze

herestring.bashใช้ herestring เพื่อส่งผ่านบรรทัดอินพุต:

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

heredoc.bashใช้ heredoc เพื่อส่งผ่านสายอินพุต:

#! /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การใช้echoและ pipe เพื่อส่งผ่านบรรทัดอินพุต:

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

สำหรับการเปรียบเทียบฉันยังตั้งเวลาสคริปต์ภายใต้ ATT ksh93 และภายใต้เส้นประ (ยกเว้นherestring.bashเพราะขีดไม่ได้มีคนนอกรีต)

นี่คือค่ามัธยฐานของสามครั้ง:

$ 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

สรุป:

  • heredoc นั้นเร็วกว่า herestring
  • echoและเห็นได้ชัดว่าเป็นท่อ แต่ไม่เร็วขึ้นอย่างมาก (โปรดทราบว่านี่คือโปรแกรมของเล่น: ในโปรแกรมจริงเวลาในการประมวลผลส่วนใหญ่จะเป็นในสิ่งที่การtrโทรมีไว้สำหรับที่นี่)
  • หากคุณต้องการความเร็วคลองทุบและโทรรีบหรือ ksh ดียิ่งขึ้นแทน คุณสมบัติของ Bash ไม่ได้ชดเชยความเชื่องช้า แต่ ksh มีทั้งคุณสมบัติและความเร็ว

นอกเหนือจากประสิทธิภาพแล้วยังมีความชัดเจนและพกพาได้อีกด้วย <<<เป็น ksh93 / ทุบตี / zsh ขยายซึ่งเป็นที่รู้จักน้อยกว่าหรือecho … | <<มันไม่ทำงานใน ksh88 / pdksh หรือใน POSIX sh

สถานที่เดียวที่<<<มีเนื้อหาที่ชัดเจนมีความหมายอยู่ใน heredoc:

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

VS

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

(เชลล์ส่วนใหญ่ไม่สามารถรับมือกับการปิดวงเล็บในตอนท้ายของบรรทัดที่มี<<EOF)


2
โปรดทราบว่า<<<มาจากและเป็นครั้งแรกที่ถูกนำไปบอร์นเหมือนเปลือกโลกrc ไม่ได้เพิ่มขึ้นบรรทัดใหม่ต่อท้าย แต่ทั้งหมดของ, และทำ AFAICT ใช้ไพพ์ในขณะนั้นและใช้ไฟล์ชั่วคราวเช่นเดียวกับ heredocs zshrcbashzshksh93rcbashzshksh93
Stéphane Chazelas

@StephaneChazelas โอ๊ะฉันควรตรวจสอบแล้วขอบคุณ สิ่งนี้ทำให้<<<มีประโยชน์น้อยลง
Gilles 'หยุดความชั่วร้าย'

นอกจากนี้โปรดทราบว่าzshมีการเพิ่มประสิทธิภาพใน=(<<<foo)การสร้างไฟล์ temp ที่มี "foo" อย่างมีประสิทธิภาพซึ่งไม่เกี่ยวข้องกับการฟอร์กกระบวนการตามที่=(echo foo)ควรจะเป็น
Stéphane Chazelas

ฉันยังจะพูดถึง pdksh ที่ไม่ได้<<<รวมถึง
kurtm

@kurtm“ มันไม่ทำงานใน ksh88 / pdksh หรือ POSIX sh”
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

6

อีกเหตุผลที่ควรใช้ heredocs (หากคุณมีไม่เพียงพอ) ก็คือเสียงสะท้อนนั้นอาจล้มเหลวหากไม่ได้ใช้กระแสข้อมูล พิจารณาpipefailตัวเลือกทุบตี:

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

/bin/trueไม่ใช้อินพุตมาตรฐาน แต่ก็echo yawnเสร็จสมบูรณ์ อย่างไรก็ตามหาก echo ถูกขอให้พิมพ์ข้อมูลจำนวนมากมันจะไม่สมบูรณ์จนกว่าจะtrueเสร็จสิ้น:

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 ถูกเพิ่มเพราะ bash ทำตาม bash (1):

เมื่อคำสั่งยุติการทำงานที่สัญญาณร้ายแรง N, bash จะใช้ค่า 128 + N เป็นสถานะออก

Heredocs ไม่มีปัญหานี้:

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

ขอบคุณความแตกต่างที่เฉพาะเจาะจงนี้ทำให้สคริปต์ของฉันล้มเหลวเป็นระยะ ๆ ในสถานที่สุ่มดังนั้นบนเซิร์ฟเวอร์ Amazon มากกว่าบนโฮสต์ kvm แต่มันก็ยังล้มเหลวเป็นครั้งคราวในที่ที่ดูเหมือนจะเป็นไปไม่ได้ เป็นเรื่องดีที่pipefailค่าเริ่มต้นที่จะถูกปิด!
mogsie

2
โอ้ฉันเปิดใช้งานpipefailที่ด้านบนของทุกสคริปต์ ลองผิดพลาดทั้งหมด!
l0b0

@ l0b0 อืมมม บางทีฉันจะเพิ่ม pipefail ไปที่j.mp/safebashฉันทุกคนได้รับข้อผิดพลาดทั้งหมดในระหว่างการพัฒนา!
Bruno Bronosky

2

เหตุผลหนึ่งที่คุณอาจต้องการใช้ echo คือการควบคุมอักขระขึ้นบรรทัดใหม่ที่เพิ่มเข้ามาในตอนท้ายของ heredocs และ herestrings:

อักขระสามตัวfooมีความยาว 3:

$ echo -n foo | wc -c
3

อย่างไรก็ตาม herestring สามตัวละครคือสี่ตัวอักษร:

$ wc -c <<< foo
4

heredoc สามตัวละครเช่นกัน:

$ wc -c << EOF
foo
EOF
4

ตัวละครที่สี่คือการขึ้นบรรทัดใหม่0x0aตัวละคร

ยังไงก็เถอะอย่างน่าอัศจรรย์กับวิธีทุบตีเอาตัวละครขึ้นบรรทัดใหม่เหล่านี้เมื่อคว้าเอาท์พุทจากเปลือกย่อย:

นี่คือคำสั่งที่ส่งกลับสี่ตัวอักษร: และfoo \n'\ n' ถูกเพิ่มโดย echo มันจะเพิ่มอักขระขึ้นบรรทัดใหม่เสมอเว้นแต่คุณจะระบุ-nตัวเลือก:

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

อย่างไรก็ตามโดยการกำหนดสิ่งนี้ให้กับตัวแปรบรรทัดใหม่ต่อท้ายที่เพิ่มโดย echo จะถูกลบ:

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

ดังนั้นหากคุณผสมไฟล์และตัวแปรและใช้ในการคำนวณ (เช่นคุณไม่สามารถใช้ heredocs หรือ herestrings ได้เนื่องจากมันจะเพิ่มบรรทัดใหม่

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

หากคุณเปลี่ยนคำสั่ง if เพื่ออ่าน

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

จากนั้นการทดสอบจะผ่าน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.