По сути, это проблема переносимости (и надежности).
Первоначально 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"
с помощью echo
in много снарядов.
На некоторых оболочках , как bash
¹ или ksh93
² или yash
( $ECHO_STYLE
переменная), поведение даже зависит от того, как оболочка была собрана или среда (ГНУ echo
«поведение s также изменится , если $POSIXLY_CORRECT
в окружающей среде и с версией 4 , zsh
» s с bsd_echo
опцией, некоторые на основе pdksh с их posix
опцией, или они называются как, sh
или нет). Таким образом, две bash
echo
пары, даже из одной и той же версии bash
, не могут вести себя одинаково.
POSIX говорит: если первый аргумент -n
или любой аргумент содержит обратную косую черту, то поведение не определено . bash
echo в этом отношении не является 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
На этот раз bash
POSIX и 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_FEATURES
7 содержит 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
встроенные функции sh
BSD или 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
встроенная в их оболочку ), где можно записать zsh
s echo -E - "$var"
или yash
s ECHO_STYLE=raw echo "$var"
( printf '%s\n' "$var"
):
/bin/echo "$var
\c"
Реализации, которые поддерживают -E
и -n
(или могут быть настроены) также могут делать:
echo -nE "$var
"
И zsh
S 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
.
echo -e
?