echo vs <<<, hoặc sử dụng tiếng vang vô dụng trong giải thưởng Bash?


18

Cho đến nay, việc sử dụng catgiải thưởng vô dụng đã rất nổi tiếng và cũng có đề cập đến việc sử dụng vô dụngecho (không liên quan đến câu hỏi này). Tôi đang tự hỏi liệu có nên sử dụng echo"Giải thưởng vô dụng trong Bash" hay không: Đường ống dường như chậm hơn nhiều so với heredocs và herestrings theo một số phép đo rất không khoa học:

  • Di truyền

    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
  • Chuyển hướng

    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

Nhìn chung, heredocs và herestrings có cùng tốc độ (đây chỉ là một bộ dữ liệu từ một số thử nghiệm) trong khi chuyển hướng chậm hơn 50%. Tôi có hiểu nhầm điều gì không, hay điều này có thể được sử dụng như một quy tắc chung cho các lệnh đọc đầu vào tiêu chuẩn trong Bash?


4
Tôi chắc chắn sẽ cho phép bạn một seqgiải thưởng " sử dụng vô ích " ;-)
Chris Down

2
Lưu ý rằng bạn nên so sánh so cat <<ENDvới so cat <<< "test string"với echo "test string" | cathoặc cat <<'END'so cat <<< 'test string'với echo 'test string' | catđể có cùng số lượng mở rộng được thực hiện bởi trình bao trên chuỗi.
manatwork

@ChrisDown Derp. Tôi luôn quên điều đó. Cảm ơn!
l0b0

@manatwork Cảm ơn, thời gian cố định và cập nhật.
l0b0

Chỉ là một sự tò mò, những gì với đại diện đó? Không phải ý định ban đầu của bạn là lặp $ reps lần $(seq $reps)sao?
manatwork

Câu trả lời:


11

Đầu tiên, hãy tập trung vào hiệu suất. Tôi đã chạy điểm chuẩn cho một chương trình hơi khác trên bộ xử lý x86_64 nhàn rỗi chạy Debian.

herestring.bash, sử dụng một herestring để vượt qua một dòng đầu vào:

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

heredoc.bash, sử dụng một di sản để vượt qua một dòng đầu vào:

#! /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, sử dụng echovà một đường ống để vượt qua một dòng đầu vào:

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

Để so sánh, tôi cũng đã tính thời gian cho các tập lệnh trong ATT ksh93 và dưới dấu gạch ngang (ngoại trừ herestring.bash, vì dấu gạch ngang không có các đoạn trích).

Đây là trung bình của ba lần:

$ 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

Kết luận:

  • Một heredoc nhanh hơn một herestring.
  • echovà một đường ống là đáng chú ý, nhưng không nhanh hơn đáng kể. (Hãy nhớ rằng đây là một chương trình đồ chơi: trong một chương trình thực tế, hầu hết thời gian xử lý sẽ là bất cứ điều gì trcuộc gọi đại diện cho ở đây.)
  • Nếu bạn muốn tốc độ, hãy bỏ bash và gọi dash hoặc thậm chí tốt hơn ksh thay thế. Các tính năng của Bash không bù đắp cho sự chậm chạp tương đối của nó, nhưng ksh có cả tính năng và tốc độ.

Ngoài hiệu suất, còn có sự rõ ràng và tính di động. <<<là một phần mở rộng ksh93 / bash / zsh ít được biết đến hơn echo … |hoặc <<. Nó không hoạt động trong ksh88 / pdksh hoặc trong POSIX sh.

Nơi duy nhất <<<được cho là rõ ràng hơn đáng kể là trong một di sản:

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

đấu với

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

(Hầu hết các shell không thể đối phó với việc đóng dấu ngoặc đơn ở cuối dòng chứa <<EOF.)


2
Lưu ý <<<đến từ rcvà lần đầu tiên được đưa đến thế giới vỏ giống như Bourne zsh. rcđã không thêm một dòng mới, nhưng tất cả bash, zshksh93làm AFAICT. rcsử dụng một ống có, trong khi bash, zshksh93sử dụng một tập tin tạm thời như cho heredocs.
Stéphane Chazelas

@StephaneChazelas Rất tiếc, tôi nên kiểm tra, cảm ơn. Điều này làm cho <<<thậm chí ít hữu ích hơn.
Gilles 'SO- ngừng trở nên xấu xa'

Cũng lưu ý rằng zshcó một tối ưu hóa =(<<<foo)để tạo hiệu quả một tệp tạm thời có chứa "foo" không liên quan đến việc tạo ra một quy trình như mong =(echo foo)muốn.
Stéphane Chazelas

Tôi cũng sẽ đề cập rằng pdksh không bao gồm <<<.
kurtm

@kurtm, Nó không hoạt động trong ksh88 / pdksh hoặc trong POSIX sh. Tiết
Gilles 'SO- ngừng trở nên xấu xa'

6

Một lý do khác để sử dụng heredocs (nếu bạn không có đủ) là tiếng vang có thể thất bại nếu luồng không được tiêu thụ. Xem xét có pipefailtùy chọn bash ' :

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

/bin/truekhông tiêu thụ đầu vào tiêu chuẩn của nó, nhưng echo yawndù sao cũng hoàn thành. Tuy nhiên, nếu echo được yêu cầu in nhiều dữ liệu, nó sẽ không hoàn thành cho đến khi truehoàn thành:

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 là SIGPIPE (128 + 13) (128 được thêm vào vì bash làm như vậy theo bash (1):

Khi một lệnh kết thúc trên tín hiệu chết người N, bash sử dụng giá trị 128 + N làm trạng thái thoát.

Heredocs không có vấn đề này:

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

Cảm ơn, sắc thái đặc biệt này đã khiến kịch bản của tôi thất bại không liên tục tại các địa điểm ngẫu nhiên, trên máy chủ Amazon nhiều hơn so với máy chủ kvm, nhưng thỉnh thoảng nó vẫn thất bại, ở những nơi dường như không thể thất bại. Đó là một điều tốt mà pipefailmặc định được tắt!
mogsie

2
Ồ, tôi kích hoạt pipefailở đầu mỗi tập lệnh. Đưa tất cả các lỗi!
l0b0

@ l0b0 Hừm. Có lẽ tôi sẽ thêm pipefail vào j.mp/safebash Tôi là tất cả để nhận tất cả các lỗi trong quá trình phát triển!
Bruno Bronosky

2

Một lý do bạn có thể muốn sử dụng tiếng vang là để kiểm soát một số ký tự dòng mới được thêm vào cuối heredocs và herestrings:

Ba ký tự foocó độ dài 3:

$ echo -n foo | wc -c
3

Tuy nhiên, một loại vi khuẩn gây bệnh có bốn ký tự:

$ wc -c <<< foo
4

Một di sản ba nhân vật quá:

$ wc -c << EOF
foo
EOF
4

Nhân vật thứ tư là một 0x0anhân vật mới .

Bằng cách nào đó, điều này kỳ diệu phù hợp với cách bash loại bỏ các ký tự dòng mới này khi lấy đầu ra từ vỏ phụ:

Đây là một lệnh trả về bốn ký tự: foo\n. '\ N' được thêm bằng tiếng vang, nó luôn thêm một ký tự dòng mới trừ khi bạn chỉ định -ntùy chọn:

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

Tuy nhiên, bằng cách gán giá trị này cho một biến, dòng mới được thêm bởi echo được loại bỏ:

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

Vì vậy, nếu bạn trộn các tệp và biến và sử dụng chúng trong các phép tính (ví dụ: bạn không thể sử dụng heredocs hoặc herestrings, vì chúng sẽ thêm một dòng mới.

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

Nếu bạn thay đổi câu lệnh if để đọc

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

sau đó kiểm tra qua.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.