Почему printf лучше, чем echo?

547

Я слышал, что printfэто лучше, чем echo. Из своего опыта я могу вспомнить только один случай, когда мне приходилось использовать, printfпотому что echoон не работал для подачи текста в какую-то программу на RHEL 5.8, но printfработал. Но, видимо, есть и другие различия, и я хотел бы узнать, что это такое, а также есть ли конкретные случаи, когда использовать один против другого.

amphibient
источник
О чем echo -e?
neverMind9
1
@ neverMind9 echo -eне работает sh, только bash(по какой-то причине), и, например, docker использует shпо умолчанию для своих RUNкоманд
Ciprian Tomoiagă
@ CiprianTomoiagă echo -eдля меня работает в терминале Android, что по сути sh.
neverMind9

Ответы:

756

По сути, это проблема переносимости (и надежности).

Первоначально echoне принимал никакой опции и ничего не расширял. Все, что он делал, это выводил свои аргументы, разделенные пробелом и заканчивающиеся символом новой строки.

Теперь кто-то подумал, что было бы хорошо, если бы мы могли делать такие вещи, как echo "\n\t"вывод символов новой строки или табуляции, или иметь возможность не выводить завершающий символ новой строки.

Затем они думали сложнее, но вместо того, чтобы добавить эту функциональность в оболочку (например, perlкогда внутри двойных кавычек \tфактически означает символ табуляции), они добавили ее echo.

Дэвид Корн понял ошибку и ввел новую форму кавычки оболочки: $'...'которая позже была скопирована bashи , zshно это было уже слишком поздно к тому времени.

Теперь, когда стандартная UNIX echoполучает аргумент, который содержит два символа, \и tвместо их вывода он выводит символ табуляции. И как только он видит \cв аргументе, он прекращает выводить (поэтому завершающий символ новой строки тоже не выводится).

Другие оболочки / производители / версии Unix решили сделать это по-другому: они добавили -eопцию расширения escape-последовательностей и -nопцию не выводить завершающий перевод строки. У некоторых есть -Eвозможность отключить escape-последовательности, у некоторых есть, -nно нет -e, список escape-последовательностей, поддерживаемых одной echoреализацией, не обязательно совпадает с поддерживаемым другой.

У Sven Mascheck есть хорошая страница, которая показывает масштаб проблемы .

В тех echoреализациях, которые поддерживают опции, обычно нет поддержки --для обозначения конца опций ( echoвстроенные в некоторые не-подобные Борну оболочки делают, и zsh поддерживает -это), поэтому, например, трудно вывести "-n"с помощью echoin много снарядов.

На некоторых оболочках , как bash¹ или ksh93² или yash( $ECHO_STYLEпеременная), поведение даже зависит от того, как оболочка была собрана или среда (ГНУ echo«поведение s также изменится , если $POSIXLY_CORRECTв окружающей среде и с версией 4 , zsh» s с bsd_echoопцией, некоторые на основе pdksh с их posixопцией, или они называются как, shили нет). Таким образом, две bash echoпары, даже из одной и той же версии bash, не могут вести себя одинаково.

POSIX говорит: если первый аргумент -nили любой аргумент содержит обратную косую черту, то поведение не определено . bashecho в этом отношении не является POSIX, например echo -e, не выводится, -e<newline>как того требует POSIX. Спецификация UNIX более строгая, она запрещает -nи требует расширения некоторых escape-последовательностей, в том числе и \cтой, которая прекращает вывод.

Эти спецификации на самом деле не приходят на помощь, поскольку многие реализации не соответствуют друг другу. Даже некоторые сертифицированные системы, такие как macOS 5 , не совместимы.

Чтобы действительно представить текущую реальность, POSIX должен фактически сказать : если первый аргумент соответствует ^-([eEn]*|-help|-version)$расширенному регулярному выражению или какой-либо аргумент содержит обратную косую черту (или символы, кодировка которых содержит кодировку символа обратной косой черты, как αв локалях, использующих кодировку BIG5), то поведение неопределенные.

В общем, вы не знаете, что echo "$var"будет выводиться, пока не убедитесь, что $varон не содержит символов обратной косой черты и не начинается с -. Спецификация POSIX на самом деле говорит нам использовать printfвместо этого в этом случае.

Так что это означает, что вы не можете использовать echoдля отображения неконтролируемых данных. Другими словами, если вы пишете сценарий, и он принимает внешние данные (от пользователя в качестве аргументов или имена файлов из файловой системы ...), вы не можете использовать его echoдля отображения.

Хорошо:

echo >&2 Invalid file.

Это не:

echo >&2 "Invalid file: $file"

(Хотя он будет работать нормально с некоторыми (не совместимыми с UNIX) echoреализациями, такими как, например bash, когда xpg_echoопция не была включена тем или иным образом, например, во время компиляции или через среду).

file=$(echo "$var" | tr ' ' _)не в порядке в большинстве реализаций (исключение составляют yashс ECHO_STYLE=raw(с той оговоркой , что yash«ы переменные не могут занимать произвольные последовательности байтов , так не произвольные имена файлов) и zsh» S echo -E - "$var"6 ).

printf, с другой стороны, является более надежным, по крайней мере, когда оно ограничено основным использованием echo.

printf '%s\n' "$var"

Выводит содержимое, за $varкоторым следует символ новой строки, независимо от того, какой символ он может содержать.

printf '%s' "$var"

Выводит его без завершающего символа новой строки.

Теперь также есть различия между printfреализациями. В POSIX есть ядро ​​функций, но есть множество расширений. Например, некоторые поддерживают %qкавычки аргументов, но как это делается, зависит от оболочки к оболочке, некоторые поддерживают \uxxxxсимволы Юникода. Поведение варьируется для printf '%10s\n' "$var"многобайтовых локалей, есть как минимум три разных результата дляprintf %b '\123'

Но, в конце концов, если вы придерживаетесь набора функций POSIX printfи не пытаетесь делать с ним что-то слишком сложное, у вас нет проблем.

Но помните, что первый аргумент - это формат, поэтому он не должен содержать переменные / неконтролируемые данные.

Более надежный echoможет быть реализован с помощью printf, например:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

Подобную оболочку (которая подразумевает создание дополнительного процесса в большинстве реализаций оболочки) можно избежать, используя local IFSмножество оболочек или написав его следующим образом:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

Примечания

1. как bash«s echoповедение может быть изменено.

Во bashвремя выполнения есть две вещи, которые управляют поведением echo(помимо enable -n echoили переопределением echoкак функции или псевдонима): xpg_echo bashопция и bashнаходится ли она в режиме posix. posixРежим можно включить, если он bashвызывается как shили если он POSIXLY_CORRECTнаходится в среде или с posixпараметром:

Поведение по умолчанию в большинстве систем:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo расширяет последовательности, поскольку UNIX требует:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Это все еще отличием -nи -e-E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

С xpg_echoи режим POSIX:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

На этот раз bashPOSIX и UNIX совместимы. Обратите внимание, что в режиме POSIX bashон по-прежнему не соответствует POSIX, поскольку не выводит -eв:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

Значения по умолчанию для xpg_echo и POSIX могут быть определены во время компиляции с --enable-xpg-echo-defaultи --enable-strict-posix-defaultопции к configureсценарию. Это обычно то, что последние версии OS / X делают, чтобы построить их /bin/sh. Никакая реализация / распространение Unix / Linux в их здравом уме, как правило, не делает этого, /bin/bashхотя . На самом деле, это не так, /bin/bashкажется , что Oracle поставляется с Solaris 11 (в дополнительном пакете), похоже, скомпилирован --enable-xpg-echo-default(это было не так в Solaris 10).

2. Как ksh93«s echoповедение может быть изменено.

В ksh93, будь то echoрасширяется экранирующие последовательности или нет , и распознает варианты зависит от содержания $PATHи / или $_AST_FEATURESпеременных окружения.

Если $PATHсодержит компонент, который содержит /5binили /xpgперед компонентом /binили, /usr/binто он ведет себя как SysV / UNIX (расширяет последовательности, не принимает параметры). Если он находит /ucbили /bsdсначала, или если $_AST_FEATURES7 содержит UNIVERSE = ucb, то он ведет себя как BSD 3 ( -eчтобы разрешить расширение, распознает -n).

По умолчанию это системно-зависимый BSD на Debian (см. Вывод builtin getconf; getconf UNIVERSEпоследних версий ksh93):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. BSD для эха -е?

Ссылка на BSD для обработки этой -eопции немного вводит в заблуждение. Большинство из этих различных и несовместимых echoповедений были все представлены в AT & T:

  • \n, \0ooo, \cВ верстаке программиста UNIX (на базе Unix V6), а остальное ( \b, \r...) в Unix System III Ref .
  • -nв Unix V7 (Деннис Ричи Реф )
  • -eв Unix V8 (Деннис Ричи Реф )
  • -Eсам по себе, возможно, изначально произошел bash(CWRU / CWRU.chlog в версии 1.13.5 упоминает, что Брайан Фокс добавил его в 1992-10-18 гг., GNU echoскопировал его вскоре после выхода в sh-utils-1.8, выпущенном 10 дней спустя)

Хотя echoвстроенные функции shBSD или BSD поддерживали -eсо дня, когда они начали использовать для этого оболочку Almquist в начале 90-х, автономная echoутилита по сей день не поддерживает их там ( FreeBSD по-echo прежнему не поддерживает -e, хотя и поддерживает -nкак Unix V7 (а также, \cно только в конце последнего аргумента)).

Обработка -eбыла добавлена к ksh93«с , echoкогда в BSD вселенной в версии ksh93r выпущен в 2006 году и могут быть отключены во время компиляции.

4. GNU echo изменение поведения в 8.31

Поскольку Coreutils 8,31 (и это совершает ), GNU echoТеперь расширяет управляющие последовательности по умолчанию , когда POSIXLY_CORRECT находится в окружающей среде, в соответствии с поведением bash -o posix -O xpg_echo«ы echoвстроенной команды (см сообщения об ошибке ).

5. macOS echo

Большинство версий macOS получили сертификацию UNIX от OpenGroup .

Их shвстроенная программа echoсовместима, так как она bash(очень старая версия) построена с xpg_echoвключенной по умолчанию, но их автономная echoутилита - нет. env echo -nничего не выводит вместо -n<newline>, env echo '\n'выводит \n<newline>вместо <newline><newline>.

Это /bin/echoтот из FreeBSD, который подавляет вывод новой строки, если первый аргумент равен -nили (с 1995 года), если последний аргумент заканчивается \c, но не поддерживает никакие другие последовательности обратной косой черты, требуемые UNIX, даже \\.

6. echoреализации, которые могут вывести произвольные данные дословно

Строго говоря, вы также можете сосчитать, что FreeBSD / macOS /bin/echoвыше (не echoвстроенная в их оболочку ), где можно записать zshs echo -E - "$var"или yashs ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var"):

/bin/echo "$var
\c"

Реализации, которые поддерживают -Eи -n(или могут быть настроены) также могут делать:

echo -nE "$var
"

И zshS echo -nE - "$var"( printf %s "$var") может быть написано

/bin/echo "$var\c"

7. _AST_FEATURESи АСТUNIVERSE

Он _AST_FEATURESне предназначен для прямой манипуляции, он используется для распространения параметров конфигурации AST во время выполнения команды. Конфигурация предназначена для выполнения через (недокументированный) astgetconf()API. Внутри ksh93, то getconfвстроенный (включено builtin getconfили вызова command /opt/ast/bin/getconf) является интерфейсомastgetconf()

Например, вам нужно builtin getconf; getconf UNIVERSE = attизменить UNIVERSEнастройку на att(вызывая echoповедение SysV среди прочего). После этого вы увидите, что $_AST_FEATURESпеременная окружения содержит UNIVERSE = att.

Стефан Шазелас
источник
13
Многие ранние разработки Unix происходили изолированно, и хорошие принципы разработки программного обеспечения, такие как «когда вы меняете интерфейс, меняете имя», не применялись.
Хенк Лангевельд,
7
Как примечание, единственное (и, возможно, единственное) преимущество echoрасширения \xпоследовательностей в противоположность оболочке как части синтаксиса цитирования заключается в том, что вы можете затем вывести NUL-байт (еще один ошибочный дизайн Unix - это разделенный нулями разделитель). строк, где половина системных вызовов (например, execve()не может принимать произвольные последовательности байтов)
Стефан Шазелас
Как вы можете видеть на веб-странице Sven Maschek, даже printfкажется, что это проблема, так как большинство реализаций делают это неправильно. Единственная правильная реализация, о которой я знаю, находится в, boshи на странице от Свена Мащека не перечисляются проблемы с нулевыми байтами через \0.
Шили
1
Это отличный ответ - спасибо @ StéphaneChazelas за то, что написали это.
JoshuaRLi
28

Возможно, вы захотите использовать printfдля его параметров форматирования. echoполезно, когда дело доходит до печати значения переменной или (простой) строки, но это все, что нужно сделать. printfможет в основном делать то, что может делать C-версия.

Пример использования и возможности:

Echo:

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

Источники:

NlightNFotis
источник
@ 0xC0000022L Я исправлен, спасибо. Я не заметил, что связался не с тем сайтом, когда пытался ответить на вопрос. Спасибо за ваш вклад и исправление.
NlightNFotis
7
Использование echoдля печати переменной может завершиться ошибкой, если значение переменной содержит метасимволы.
Кит Томпсон
17

Одним из «преимуществ», если вы хотите так его назвать, будет то, что вам не нужно говорить, что он echoинтерпретирует определенные escape-последовательности, такие как \n. Он знает, как их интерпретировать, и для этого не требуется -e.

printf "some\nmulti-lined\ntext\n"

(NB: последнее \nнеобходимо, echoподразумевается, если вы не предоставите -nопцию)

против

echo -e "some\nmulti-lined\ntext"

Обратите внимание на последний \nв printf. В конце концов, это вопрос вкуса и требований, что вы используете: echoили printf.

0xC0000022L
источник
1
Верный /usr/bin/echoи bashвстроенный. dash, kshИ zshвстроенный echoне нужно -eпереключиться на расширение обратной косой чертой символов.
manatwork
Почему пугают цитаты? Ваша формулировка подразумевает, что это не обязательно реальное преимущество.
Кит Томпсон,
2
@KeithThompson: на самом деле все они предназначены , чтобы подразумевать, что не каждый может считать преимуществом.
0xC0000022L
Не могли бы вы рассказать об этом подробнее? Почему это не будет преимуществом? Фраза «если вы хотите это так называть» подразумевает, что вы думаете, что это не так.
Кит Томпсон
2
Или вы могли бы просто сделать printf '%s\n' 'foo\bar.
nyuszika7h
6

Одним из недостатков printfявляется производительность, потому что встроенная оболочка echoнамного быстрее. Это особенно заметно в Cygwin, где каждый экземпляр новой команды вызывает большие накладные расходы Windows. Когда я изменил свою эхо-программу с использования /bin/echoна эхо оболочки, производительность почти удвоилась. Это компромисс между мобильностью и производительностью. Это не хлам, чтобы всегда использовать printf.

Джон
источник
15
printfв настоящее время встроен в большинство оболочек (bash, dash, ksh, zsh, yash, некоторые производные pdksh ... включает в себя оболочки, обычно встречающиеся и в cygwin). Единственными заметными исключениями являются некоторые pdkshпроизводные.
Стефан Шазелас
Но многие из этих реализаций printf не работают. Это важно, когда вы хотите использовать printfдля вывода нулевых байтов, но некоторые реализации интерпретируют `\ c 'в строке формата, даже если они не должны.
Шили
2
@schily, printfPOSIX не определяет поведение POSIX, если вы используете \cв строке формата, поэтому printfреализации могут делать все, что захотят в этом отношении. Например, некоторые обрабатывают его так же, как в PWB echo(приводит к выходу printf), ksh использует его для \cAуправляющих символов (для аргумента формата printf и для, $'...'но не для echonor print!). Не уверен, что это связано с печатью байтов NUL, или, может быть, вы ссылаетесь на kshs printf '\c@'?
Стефан Шазелас