echo vs <<< или Бесполезное использование echo в Bash Award?

19

К настоящему времени « Бесполезное использование catнаграды» очень хорошо известно, и есть также упоминание о « Бесполезном использовании»echo (не относится к этому вопросу). Я задаюсь вопросом, должно ли быть «Бесполезное использование echoв Bash Award»: трубопроводы, кажется, намного медленнее, чем 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?

l0b0
источник
4
Я, безусловно, предоставлю вам награду за «бесполезное использование seq» ;-)
Крис Даун
2
Обратите внимание , что вы должны либо сравнивать cat <<ENDпротив cat <<< "test string"против echo "test string" | catили cat <<'END'против cat <<< 'test string'против echo 'test string' | catиметь одинаковое количество разложения в исполнении оболочки на струнах.
manatwork
@ChrisDown Derp. Я всегда забываю это. Благодарность!
10
@manatwork Спасибо, исправлены и обновлены тайминги.
10
Просто любопытство, что с этими представителями? Не было ли ваше первоначальное намерение циклически повторяться с $ reps $(seq $reps)?
manatwork

Ответы:

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и трубу для прохождения строки ввода:

#! /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 и под dash (кроме herestring.bash , что у dash нет этих строк).

Вот в среднем три раза:

$ 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 того, что здесь обозначает вызов.)
  • Если вам нужна скорость, бросьте bash и назовите dash или даже лучше ksh. Функции Bash не компенсируют его относительную медлительность, но у 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за Bourne-подобного мира ракушек zsh. rcне добавляю завершающий символ новой строки, но все bash, zshи ksh93делаю AFAICT. rcиспользует трубу там, в то время bash, zshи ksh93использовать временный файл , как для heredocs.
Стефан Шазелас
@ StephaneChazelas Ой, я должен был проверить, спасибо. Это делает <<<еще менее полезным.
Жиль "ТАК - перестань быть злым"
Также обратите внимание, что zshесть оптимизация =(<<<foo)для эффективного создания временного файла, который содержит «foo», который не включает в себя разветвление процесса, как это =(echo foo)было бы.
Стефан Шазелас
Я также упомяну, что pdksh не включает <<<.
Куртм
@kurtm «Это не работает в ksh88 / pdksh или в POSIX sh.»
Жиль «ТАК - перестать быть злым»
6

Другая причина использовать heredocs (если вам не хватило), что эхо может потерпеть неудачу, если поток не используется. Рассмотрим pipefailвариант bash :

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
mogsie
источник
Спасибо, этот специфический нюанс приводил к тому, что мой скрипт периодически прерывался в случайных местах, причем в большей степени на серверах Amazon, чем на хостах kvm, но время от времени он по-прежнему выходил из строя, в местах, которые казалось бы невозможными. Хорошо, что по pipefailумолчанию отключено!
mogsie
2
О, я включаю pipefailв верхней части каждого сценария. Дай мне все ошибки!
10
@ l0b0 Хммм. Может быть, я добавлю pipefail в j.mp/safebash Я за то, что все ошибки были получены во время разработки!
Бруно Броноски
2

Одной из причин, по которой вы можете захотеть использовать echo, является использование некоторого элемента управления символом новой строки, который добавляется в конец heredocs и herestrings:

Три символа 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

Однако, присваивая это переменной, завершающий символ новой строки, добавленный 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

тогда тест проходит.

mogsie
источник