Почему printf лучше, чем эхо?

Используя массивы удара

while read -r -a words; do
    prefix="${words[0]} ${words[1]}"
    idx=${#words[*]}
    suffix="${words[$((idx-2))]} ${words[$((idx-1))]}"
    unset words[0] words[1] words[$((idx-2))] words[$((idx-1))]
    middle="${words[*]}"
    printf "%s %-50s %s\n" "$prefix" "${middle:0:50}" "$suffix"
done < filename
556
15.05.2015, 04:44
4 ответа

В основном это - мобильность (и надежность) проблема.

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

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

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

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

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

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

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

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

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

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

Те спецификации действительно не приходят на помощь здесь, учитывая, что много реализаций не совместимы. Даже некоторые сертифицированные системы как macOS5 не совместимы.

Для реального представления текущей действительности POSIX должен на самом деле сказать: если первый аргумент соответствует ^-([eEn]*|-help|-version)$ расширенный regexp или любой аргумент содержат обратные косые черты (или символы, кодирование которых содержит кодирование символа обратной косой черты как α в локалях с помощью набора символов 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 echo -E - "$var"6).

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

printf '%s\n' "$var"

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

printf '%s' "$var"

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

Теперь, также существуют различия между printf реализации. Существует ядро функций, которое указано POSIX, но затем существует много расширений. Например, некоторая поддержка a %q заключить аргументы в кавычки, но как это сделано, варьируется от оболочки до оболочки, некоторой поддержки \uxxxx для unicode символов. Поведение варьируется для 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
     if [ "$#" -gt 0 ]; then
       printf ' %s' "$@"
     fi
  fi
  printf '\n'
}

Примечания

1. как bash 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 поставки с Солярисом 11 (в дополнительном пакете), кажется, создается с --enable-xpg-echo-default (который не имел место в Солярисе 10).

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

В ksh93, ли echo разворачивает escape-последовательности или не и распознает, что опции зависят от содержания $PATH и/или $_AST_FEATURES переменные среды.

Если $PATH содержит компонент, который содержит /5bin или /xpg перед /bin или /usr/bin компонент затем, это ведет себя путь SysV/UNIX (разворачивает последовательности, не принимает опции). Если это находит /ucb или /bsd сначала или если $_AST_FEATURES7 содержит UNIVERSE = ucb, затем это ведет себя путь BSD3 (-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 для эха-e?

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

  • \n, \0ooo, \c в Инструментальных средствах Программиста UNIX (на основе Unix V6), и остальные (\b, \r...) в системе Unix IIIRef.
  • -n в Unix V7 (Dennis RitchieRef)
  • -e в Unix V8 (Dennis RitchieRef)
  • -E самостоятельно возможно первоначально прибыл из bash (CWRU/CWRU.chlog в версии 1.13.5 упоминает Brian Fox, добавляющего его 18.10.1992, GNU echo копирование его вскоре после в sh-utils-1.8 выпустило 10 дней спустя),

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

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

4. Изменение эха GNU поведения в 8,31

Начиная с coreutils 8.31 (и эта фиксация), GNU echo теперь разворачивает escape-последовательности по умолчанию, когда 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 echo -E - "$var" или yash ECHO_STYLE=raw echo "$var" (printf '%s\n' "$var") мог быть записан:

/bin/echo "$var
\c"

И zsh echo -nE - "$var" (printf %s "$var") мог быть записан

/bin/echo "$var\c"

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

echo -nE "$var
"

Для эквивалента printf '%s\n' "$var".

7. _AST_FEATURES и AST 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.

775
27.01.2020, 19:27
  • 1
    , Большая ранняя разработка Unix произошла в изоляции и хороших принципах разработки программного обеспечения как, 'когда Вы изменяете интерфейс, меняете имя', не были применены. –  Henk Langeveld 14.03.2014, 09:57
  • 2
    Как примечание, одно (и возможно только) преимущество наличия echo расширьтесь \x последовательности в противоположность оболочке как часть синтаксиса заключения в кавычки - то, что можно затем произвести байт NUL (другой возможно, неправильный дизайн Unix является теми строками разграниченных пустым указателем, где половина системных вызовов (как execve()) не может взять произвольные последовательности байтов), –  Stéphane Chazelas 04.08.2015, 15:15

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

Использование в качестве примера и возможности:

Echo:

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

printf:

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

Источники:

28
27.01.2020, 19:27
  • 1
    Используя echo распечатать переменную может перестать работать, если значение переменной содержит метасимволы. –  Keith Thompson 29.04.2013, 18:22

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

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

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

по сравнению с

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

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

17
27.01.2020, 19:27
  • 1
    , Верный для /usr/bin/echo и bash встроенный. dash, ksh и zsh встроенный echo не нуждается -e переключатель для расширения оставленных из обратной косой черты символов. –  manatwork 23.02.2013, 12:35
  • 2
    Почему паника заключает в кавычки? Ваша формулировка подразумевает, что это - не обязательно реальное преимущество. –  Keith Thompson 29.04.2013, 18:26
  • 3
    @KeithThompson: на самом деле все, что они предназначены для допущения, - то, что не все могут считать это преимуществом. –  0xC0000022L 29.04.2013, 18:33
  • 4
    Вы могли подробно остановиться на этом? Почему это не было бы преимущество? Фраза, "если Вы хотите назвать его, который" подразумевает довольно сильно, что Вы думаете, что это не. –  Keith Thompson 29.04.2013, 18:36
  • 5
    Или Вы могли просто сделать printf '%s\n' 'foo\bar. спасибо –  nyuszika7h 09.03.2014, 19:36

Одним из недостаток PrintF - это производительность, поскольку встроенная оболочка ECHO намного быстрее. Это входит в игру, особенно в Cygwin, где каждый экземпляр новой команды вызывает накладные расходы на тяжелые окна. Когда я изменил свою эхо-тяжелую программу с использованием / BIN / ECHO к эхо-эхоте, производительность почти удвоилось. Это торговля между портативностью и производительностью. Это не удам данка, чтобы всегда использовать Printf .

6
27.01.2020, 19:27

Теги

Похожие вопросы