Чому printf кращий за відлуння?


548

Я чув, що printfце краще, ніж echo. Я можу згадати лише один екземпляр зі свого досвіду, коли мені довелося скористатися, printfоскільки echoне працювало для подачі тексту в якусь програму на RHEL 5.8, але printfце було. Але, мабуть, є й інші відмінності, і я хотів би дізнатися, що вони таке, а також, якщо є конкретні випадки, коли потрібно використовувати один проти іншого.


Чому echo -e?
neverMind9

1
@ neverMind9 echo -eне працює sh, тільки в bash(чомусь), а docker, наприклад, використовує shза замовчуванням свої RUNкоманди
Ciprian Tomoiagă

@ CiprianTomoiagă echo -eдля мене працює в Android Terminal, який по суті є sh.
neverMind9

Відповіді:


757

В основному, це питання портативності (та надійності).

Спочатку echoне прийняв жодного варіанту і нічого не розширив. Все, що вона робила, було виводити свої аргументи, розділені пробільним символом та завершені символом нового рядка.

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

Потім вони думали важче, але замість того, щоб додавати цю функціональність до оболонки (наприклад, perlде всередині подвійних лапок, \tнасправді означає символ вкладки), вони додали її echo.

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

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

Інші оболонки / постачальники / версії Unix вирішили зробити це інакше: вони додали -eопцію розширення послідовностей евакуації та -nопцію не виводити проміжний новий рядок. Деякі мають -Eвідключити послідовності відходу, деякі мають, -nале ні -e, перелік послідовностей евакуації, що підтримується однією echoреалізацією, не обов'язково такий же, як підтримуваний іншим.

У Свена Машека є приємна сторінка, яка показує масштаб проблеми .

У тих echoреалізаціях , які підтримують варіанти, немає взагалі ніякої підтримки з боку , --щоб відзначити кінець опцій (на echoвбудовану команду декого не-Борн-подібних оболонок роблять, і ЗШ підтримує -для цього , хоча), так, наприклад, це важко вивести "-n"з echoв багато снарядів.

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

POSIX говорить: якщо перший аргумент є -nабо будь-який аргумент містить зворотні косої риси, то поведінка не визначено . bashвідлуння в цьому плані не POSIX, оскільки, наприклад echo -e, не виводиться так, -e<newline>як вимагає POSIX. Специфікація UNIX суворіша, вона забороняє -nі вимагає розширення деяких послідовностей евакуації, включаючи таку, \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's, коли 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, але тоді є багато розширень. Наприклад, деякі підтримують 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
  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_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 (від Dennis Ritchie Ref )
  • -eв Unix V8 (від Dennis Ritchie Ref )
  • -Eсам, можливо, спочатку походив bash(CWRU / CWRU.chlog у версії 1.13.5 згадує Брайана Фокса, додаючи його 1992-10-18, GNU echoкопіює його незабаром після того, як в sh-utils-1.8 випущено 10 днів пізніше)

Хоча echoвбудовані shабо BSD підтримуються -eз дня, коли вони почали використовувати оболонку Almquist для неї на початку 90-х, автономна echoутиліта до цього дня не підтримує її ( FreeBSDecho досі не підтримує -e, хоча вона підтримує, -nяк Unix V7 (і також, \cале лише в кінці останнього аргументу)).

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

4. Зміни поведінки GHU у 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"або 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.


13
Багато ранньої розробки Unix відбулося поодиноко, і не застосовувалися хороші принципи інженерії програмного забезпечення, такі як "коли ви змінюєте інтерфейс, змінюєте ім'я" .
Хенк Лангевельд

7
Як зауваження, одна (і, можливо, лише) перевага echoрозширення \xпослідовностей на відміну від оболонки як частини синтаксису цитування полягає в тому, що потім можна вивести байт NUL (інший, мабуть, неправильний дизайн Unix - це ті, що не встановлені з нулем рядки, де половина системних викликів (на зразок execve()) не може приймати довільні послідовності байтів)
Stéphane Chazelas

Як ви можете бачити на веб-сторінці від Sven Maschek, це навіть printfздається проблемою, оскільки більшість реалізацій роблять це неправильно. Я знаю єдину правильну реалізацію, boshі на сторінці Sven Maschek не вказано проблем з нульовими байтами via \0.
шилі

1
Це чудова відповідь - дякую @ StéphaneChazelas, що написав це.
ДжошуаРЛІ

28

Можливо, ви хочете використовувати printfйого параметри форматування. echoкорисно, коли мова йде про друк значення змінної чи (простого) рядка, але це все, що в ньому є. printfв основному може робити те, що може зробити C версія.

Приклад використання та можливостей:

Echo:

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

printf:

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

Джерела:


@ 0xC0000022L Я виправляю спасибі. Я не помітив, що я поспішив на неправильний веб-сайт у поспіху відповісти на питання. Дякую за ваш внесок та виправлення.
NlightNFotis

7
Використовувати echoдля друку змінної може не вдатися, якщо значення змінної містить метасимволи.
Кіт Томпсон

17

Однією з "переваг", якщо ви хочете назвати це так, є те, що вам не доведеться говорити це як echoінтерпретувати певні послідовності втечі, такі як \n. Він знає, як їх інтерпретувати, і не вимагає цього -eробити.

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

(Примітка: останнє \nнеобхідно, echoмається на увазі, якщо ви не надаєте -nваріант)

проти

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

Зверніть увагу на останній \nв printf. Зрештою, справа в смаку та вимогах, що ви використовуєте: echoабо printf.


1
Правда /usr/bin/echoі для bashвбудованого. dash, kshІ zshвбудований echoне потрібно -eпереключитися на розширення зворотною косою межею символів.
манатура

Чому лякають цитати? Ваше формулювання означає, що це не обов'язково є реальною перевагою.
Кіт Томпсон

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, printfякщо ви використовуєте \cв рядку формату , поведінку не визначено POSIX , тому printfреалізація може робити все, що завгодно в цьому плані. Наприклад, деякі трактують це так само, як і в PWB echo(викликає вихід printf), ksh використовує його для \cAкеруючих символів (для аргументу формату printf і для, $'...'але не для echonor print!). Не впевнені, що це стосується друку байтів NUL, чи, можливо, ви попрацюєте kshз ними printf '\c@'?
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.