echo vs <<<, або марне використання відлуння в Bash Award?


19

На сьогоднішній день Безкорисне використання catнагороди дуже відоме, а також згадка про марне використанняecho (не стосується цього питання). Мені цікаво, чи не повинно бути "Марно використання echoБаш-премії": Труби здаються набагато повільнішими, ніж гередоки та ересті, згідно з деякими високонауковими вимірюваннями:

  • Гередоки:

    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
    

Взагалі гередоки та герестини мають однакову швидкість (це лише один набір даних із декількох тестів), тоді як перенаправлення постійно більш ніж на 50% повільніше. Я щось не розумію, чи це можна використовувати як загальне правило для команд, що читають стандартний вхід в Bash?


4
Я, безумовно, дозволю вам seqнагороду за "марне використання " ;-)
Кріс Даун

2
Зверніть увагу , що ви повинні або порівнювати cat <<ENDпроти cat <<< "test string"проти echo "test string" | catабо cat <<'END'проти cat <<< 'test string'проти echo 'test string' | catмати однакову кількість розкладання в виконанні оболонки на струнах.
манатура

@ChrisDown Derp. Я завжди забуваю це. Спасибі!
l0b0

@manatwork Спасибі, встановлені та оновлені таймінги.
l0b0

Просто цікавість, що з тими повторами? Чи не був ваш початковий намір зациклювати $ повторень $(seq $reps)?
манастирство

Відповіді:


11

Спочатку давайте зосередимось на продуктивності. Я застосував орієнтири для дещо іншої програми на інакше переважно непрацюючому процесорі x86_64, на якому працює Debian стискання.

herestring.bash, використовуючи херестинг для передачі рядка введення:

#! /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і трубу для передачі лінії введення:

#! /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

Висновки:

  • Гередок швидший за херест.
  • echoі труба помітно, але не різко швидша. (Майте на увазі, що це іграшкова програма: в реальній програмі більшість часу на обробку буде приділятися тому, що тут trвиступає.)
  • Якщо ви хочете швидкості, вирийте канаву та зателефонуйте тире або ще краще ksh замість цього. Особливості Баша не компенсують його відносної повільності, але ksh має і функції, і швидкість.

Крім продуктивності, є також чіткість і портативність. <<<- розширення ksh93 / bash / zsh, яке менш відоме, ніж echo … |або <<. Він не працює в ksh88 / pdksh або в POSIX sh.

Єдине місце, де <<<, напевно, значно зрозуміліше - це всередині гередок:

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

проти

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

(Більшість оболонок не справляється із закриттям дужок у кінці рядка, що містить <<EOF.)


2
Зауважимо, що <<<походить з rcі вперше був завезений у світ Борна, подібного до мушлі zsh. rcне додав останній новий рядок, але все bash, zshі ksh93зробив AFAICT. rcвикористовує трубу там, в той час bash, zshі ksh93використовувати тимчасовий файл , як для heredocs.
Стефан Шазелас

@StephaneChazelas На жаль, я повинен був перевірити, дякую. Це робить <<<ще менш корисними.
Жил "ТАК - перестань бути злим"

Також зауважте, що zshє оптимізація =(<<<foo)для ефективного створення тимчасового файлу, який містить "foo", який не передбачає форкінг процесу як =(echo foo)би.
Стефан Шазелас

Я також зазначу, що pdksh не включає <<<.
kurtm

@kurtm "Це не працює ні в ksh88 / pdksh, ні в POSIX sh."
Жил "SO- перестань бути злим"

6

Ще одна причина використовувати гередоки (якщо вас не вистачало) - це те, що ехо може вийти з ладу, якщо потік не споживається. Розглянемо pipefailваріант bash ' :

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

/bin/trueне споживає свого стандартного введення, але echo yawnвсе-таки завершує. Однак якщо відлуння буде запропоновано надрукувати багато даних, воно не завершиться, поки 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 як стан виходу.

У гередоків немає цієї проблеми:

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

Дякую, саме цей нюанс спричинив час відмови мого сценарію у випадкових місцях, тим більше на серверах Amazon, ніж на хостах kvm, але він все ще час від часу провалювався у місцях, здавалося б, неможливих відмов. Добре, що за pipefailзамовчуванням вимкнено!
mogsie

2
О, я включаю pipefailвгорі кожного сценарію. Дайте всі помилки!
l0b0

@ l0b0 Гммм. Можливо, я додам pipefail до j.mp/safebash Я все для отримання всіх помилок під час розробки!
Бруно Броноський

2

Однією з причин, за якою ви хочете скористатися echo, є те, щоб здійснити деякий контроль над символом нового рядка, який додається в кінці гередоків і єрестурів:

Три символи fooмають довжину 3:

$ echo -n foo | wc -c
3

Однак трихарактерна герестрира - це чотири символи:

$ wc -c <<< foo
4

Три герадок теж:

$ wc -c << EOF
foo
EOF
4

Четвертий символ - це символ нового рядка 0x0a.

Так чи інакше це магічно вписується в те, як Bash видаляє ці символи нового рядка під час захоплення виводу з підрозділу:

Ось команда, яка повертає чотири символи: fooі \n. '\ N' додається echo, воно завжди додає символ нового рядка, якщо не вказати -nпараметр:

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

Однак, призначивши це змінної, останній рядок, доданий ехо, видаляється:

$ 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

потім тест проходить.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.