Dlaczego printf jest lepszy niż echo?

548

Słyszałem, że printfto lepsze niż echo. Mogę przypomnieć sobie tylko jedną instancję z mojego doświadczenia, z której musiałem skorzystać, printfponieważ echonie działałem do dostarczenia tekstu do jakiegoś programu na RHEL 5.8, ale to printfzrobiłem. Ale najwyraźniej istnieją inne różnice i chciałbym dowiedzieć się, jakie one są, a także czy istnieją konkretne przypadki, w których można użyć jednego kontra drugiego.

amfibia
źródło
O czym echo -e?
neverMind9
1
@ neverMind9 echo -enie działa sh, tylko w bash(z jakiegoś powodu), a na przykład shRUN
docker
@ CiprianTomoiagă echo -edla mnie działa w Android Terminal, czyli w zasadzie sh.
neverMind9

Odpowiedzi:

757

Zasadniczo jest to kwestia przenośności (i niezawodności).

Początkowo echonie akceptowałem żadnej opcji i niczego nie rozwijałem. Wszystko, co robił, to wypisywanie argumentów oddzielonych znakiem spacji i kończonych znakiem nowej linii.

Teraz ktoś pomyślał, że byłoby miło, gdybyśmy mogli zrobić takie rzeczy, jak echo "\n\t"wypisywanie znaków nowej linii lub tabulacji, lub mieć opcję, aby nie wypuszczać końcowego znaku nowej linii.

Następnie zastanowili się mocniej, ale zamiast dodania tej funkcji do powłoki (tak jak perltam, gdzie podwójne cudzysłowy \tfaktycznie oznaczają znak tabulacji), dodali ją echo.

David Korn sobie sprawę z błędu i wprowadził nową formę cytatów powłoki: $'...'który został później kopiowane przez basha zshale było zbyt późno do tego czasu.

Teraz, gdy standardowy UNIX echootrzymuje argument zawierający dwa znaki \i tzamiast wypisywać je, wypisuje znak tabulacji. I gdy tylko zobaczy \cargument, przestaje wypisywać (więc końcowy znak nowej linii też nie jest wypisywany).

Inni dostawcy powłok / wersji Unixa wybrali to inaczej: dodali -eopcję rozwijania sekwencji specjalnych i -nopcję nie wysyłania końcowego znaku nowej linii. Niektóre muszą -Ewyłączyć sekwencje specjalne, niektóre mają, -nale nie -e, lista sekwencji specjalnych obsługiwanych przez jedną echoimplementację niekoniecznie jest taka sama jak obsługiwana przez inną.

Sven Mascheck ma ładną stronę, która pokazuje skalę problemu .

Z powyższych echoimplementacjach że opcje pomocy, tam zazwyczaj nie poparcie --, aby zaznaczyć koniec opcji (na echopolecenie wbudowane niektórych non-Bourne podobnych muszli zrobienia i zsh obsługuje -do tego jednak), więc na przykład, trudno jest wyjście "-n"z echow wiele muszli.

W niektórych muszli jak bashđ lub ksh93² lub yash( $ECHO_STYLEzmienna), zachowanie nawet zależy powłoki opracowano lub środowiska (GNU echo"zachowania s będą także zmieniać $POSIXLY_CORRECTsię w środowisku, w wersji 4 , zshjest z bsd_echoopcją niektóre oparte na pdksh z ich posixopcją lub czy są wywoływane jako shczy nie). Tak więc dwa bash echos, nawet z tej samej wersji, bashnie mają gwarancji, że zachowają się tak samo.

POSIX mówi: jeśli pierwszy argument -nlub dowolny argument zawiera ukośniki odwrotne, zachowanie jest nieokreślone . bashecho w tym względzie nie jest POSIX, ponieważ na przykład echo -enie jest generowane, -e<newline>jak wymaga POSIX. Specyfikacja UNIX jest bardziej rygorystyczna, zabrania -ni wymaga rozszerzania niektórych sekwencji ucieczki, w tym tej, \caby zatrzymać wysyłanie.

Te specyfikacje tak naprawdę nie przydają się tutaj, biorąc pod uwagę, że wiele implementacji jest niezgodnych. Nawet niektóre certyfikowane systemy, takie jak macOS 5, nie są zgodne.

Aby naprawdę przedstawić aktualną rzeczywistość, POSIX powinien w rzeczywistości powiedzieć : jeśli pierwszy argument pasuje do ^-([eEn]*|-help|-version)$rozszerzonego αwyrażenia regularnego lub dowolny argument zawiera odwrotne ukośniki (lub znaki, których kodowanie zawiera kodowanie znaku odwrotnego ukośnika, tak jak w lokalizacjach używających zestawu znaków BIG5), wówczas zachowanie jest następujące: nieokreślony.

Podsumowując, nie wiesz, co echo "$var"wyświetli, chyba że możesz upewnić się, że $varnie zawiera znaków odwrotnego ukośnika i nie zaczyna się od -. W tym przypadku specyfikacja POSIX mówi nam, abyśmy printfzamiast tego używali .

Oznacza to, że nie można używać echodo wyświetlania niekontrolowanych danych. Innymi słowy, jeśli piszesz skrypt i pobiera on dane zewnętrzne (od użytkownika jako argumenty lub nazwy plików z systemu plików ...), nie możesz go użyć echodo wyświetlenia.

To jest wporządku:

echo >&2 Invalid file.

To nie jest:

echo >&2 "Invalid file: $file"

(Chociaż będzie działać OK z niektórymi (niezgodnymi z UNIX) echoimplementacjami, takimi jak bashs, gdy xpg_echoopcja nie została włączona w taki czy inny sposób, jak w czasie kompilacji lub przez środowisko).

file=$(echo "$var" | tr ' ' _)nie jest w porządku w większości implementacji (wyjątkami są yashz ECHO_STYLE=raw(z zastrzeżeniem, że yash„s zmienne nie mogą posiadać dowolne sekwencje bajtów, więc nie arbitralne nazwy pliku) i zsh” s echo -E - "$var"6 ).

printf, z drugiej strony jest bardziej niezawodny, przynajmniej gdy ogranicza się do podstawowego użycia echo.

printf '%s\n' "$var"

Wyświetla zawartość $varznaku, po którym następuje znak nowej linii, niezależnie od tego, jaki znak może zawierać.

printf '%s' "$var"

Wyprowadzi go bez końcowego znaku nowej linii.

Istnieją także różnice między printfimplementacjami. Istnieje rdzeń funkcji określonych przez POSIX, ale istnieje wiele rozszerzeń. Na przykład niektórzy popierają a, %qaby zacytować argumenty, ale sposób wykonania różni się w zależności od powłoki, niektóre obsługują \uxxxxznaki Unicode. Zachowanie różni się printf '%10s\n' "$var"w przypadku wielobajtowych lokalizacji, istnieją co najmniej trzy różne wynikiprintf %b '\123'

Ale w końcu, jeśli trzymasz się zestawu funkcji POSIX printfi nie próbujesz robić z nim nic szczególnego, nie masz problemów.

Pamiętaj jednak, że pierwszym argumentem jest format, więc nie powinien zawierać zmiennych / niekontrolowanych danych.

Bardziej niezawodny echomożna wdrożyć za pomocą 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' "$*"
)

Podpowłoki (która implikuje spawnowanie dodatkowego procesu w większości implementacji powłoki) można uniknąć za local IFSpomocą wielu powłok lub pisząc je w następujący sposób:

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

Uwagi

1. jaki bashjest echozachowanie może być zmienione.

W bashczasie wykonywania istnieją dwie rzeczy, które kontrolują zachowanie echo(obok enable -n echolub przedefiniowanie echojako funkcji lub aliasu): xpg_echo bashopcja i bashto, czy jest w trybie POSIX. posixTryb można włączyć, jeśli bashjest wywoływany jako shlub POSIXLY_CORRECTw środowisku lub z posixopcją:

Domyślne zachowanie w większości systemów:

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

xpg_echo rozwija sekwencje, ponieważ UNIX wymaga:

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

Nadal honoruje -ni -e(i -E):

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

W xpg_echotrybie POSIX i:

$ 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

Tym razem bashjest zgodny zarówno z POSIX, jak i UNIX. Zauważ, że w trybie POSIX bashwciąż nie jest zgodny z POSIX, ponieważ nie wyświetla danych wyjściowych -e:

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

$

Domyślne wartości xpg_echo i posix można zdefiniować w czasie kompilacji za pomocą opcji --enable-xpg-echo-defaulti skryptu. Tak zwykle robią ostatnie wersje OS / X, aby je zbudować . Jednak żadne wdrożenie / dystrybucja Uniksa / Linuksa przy zdrowych zmysłach zazwyczaj tego nie zrobiłoby . W rzeczywistości nie jest to prawdą, że Oracle jest dostarczane z Solaris 11 (w pakiecie opcjonalnym), jak się wydaje (tak nie było w Solarisie 10).--enable-strict-posix-defaultconfigure/bin/sh/bin/bash/bin/bash--enable-xpg-echo-default

2. Jak ksh93jest echozachowanie może być zmienione.

W ksh93, czy echorozwija sekwencje specjalne czy nie i rozpoznaje opcje, zależy od zawartości zmiennych środowiskowych $PATHi / lub $_AST_FEATURES.

Jeśli $PATHzawiera komponent, który zawiera /5binlub /xpgprzed komponentem /binlub /usr/bin, wówczas zachowuje się w sposób SysV / UNIX (rozwija sekwencje, nie akceptuje opcji). Jeśli znajdzie /ucblub /bsdpierwszy lub jeśli $_AST_FEATURES7 zawiera UNIVERSE = ucb, zachowuje się w BSD 3 sposób ( -eaby umożliwić ekspansję, rozpoznaje -n).

Domyślnie jest zależne od systemu, BSD na Debianie (zobacz dane wyjściowe builtin getconf; getconf UNIVERSEw najnowszych wersjach 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 dla echa -e?

Odniesienie do BSD do obsługi -eopcji jest tutaj nieco mylące. Większość tych różnych i niekompatybilnych echozachowań została wprowadzona w AT&T:

  • \n, \0ooo, \cW programisty stole warsztatowym UNIX (w oparciu o Unix V6), a reszta ( \b, \r...) w System III sygn .
  • -nw Unix V7 (autor: Dennis Ritchie Ref )
  • -ew Unix V8 (autor: Dennis Ritchie Ref )
  • -Esam prawdopodobnie początkowo pochodzi z bash(CWRU / CWRU.chlog w wersji 1.13.5 wspomina Briana Foxa dodającego go w dniach 1992-10-18, GNU echokopiuje go wkrótce w sh-utils-1.8 wydanym 10 dni później)

Chociaż echowbudowane shlub BSD są obsługiwane -eod dnia, w którym zaczęły używać powłoki Almquist na początku lat 90., samodzielne echonarzędzie do dziś tego nie obsługuje ( FreeBSDecho nadal nie obsługuje -e, chociaż obsługuje -ntakie jak Unix V7 (i także, \cale tylko na końcu ostatniego argumentu)).

Obsługa -ezostała dodana do ksh93, echogdy we wszechświecie BSD w wersji ksh93r wydanej w 2006 roku i można ją wyłączyć w czasie kompilacji.

4. Zmiana zachowania echa GNU w 8.31

Od coreutils 8,31 (i to popełniają ), GNU echoteraz rozszerza sekwencje domyślnie po POSIXLY_CORRECT w środowisku, aby dopasować zachowanie bash -o posix -O xpg_echo„s echopolecenie wbudowane (patrz raport o błędzie ).

5. macOS echo

Większość wersji macOS otrzymała certyfikat UNIX od OpenGroup .

Ich shwbudowane echojest zgodne, ponieważ jest bash(bardzo stara wersja) wbudowane z xpg_echodomyślnie włączoną, ale ich samodzielne echonarzędzie nie jest. env echo -nwypisuje nic zamiast -n<newline>, env echo '\n'wypisuje \n<newline>zamiast <newline><newline>.

To /bin/echojest ten z FreeBSD, który tłumi wyjście nowej linii, jeśli pierwszy argument to -nlub (od 1995), jeśli ostatni argument się kończy \c, ale nie obsługuje żadnych innych sekwencji odwrotnego ukośnika wymaganych nawet przez UNIX \\.

6. echoimplementacje, które mogą generować dowolne dane dosłownie

Ściśle mówiąc, można również liczyć, że FreeBSD / macOS /bin/echopowyżej (nie jest to echowbudowane w ich powłokę ), gdzie można zapisać zsh' echo -E - "$var"lub yash' ECHO_STYLE=raw echo "$var"( s printf '%s\n' "$var"):

/bin/echo "$var
\c"

Implementacje, które obsługują -Ei -n(lub można je skonfigurować) mogą również wykonywać:

echo -nE "$var
"

I zsh„s echo -nE - "$var"( printf %s "$var") można zapisać

/bin/echo "$var\c"

7. _AST_FEATURESi ASTUNIVERSE

Nie _AST_FEATURESjest przeznaczony do bezpośredniej manipulacji, służy do propagowania ustawień konfiguracji AST podczas wykonywania poleceń. Konfiguracja ma zostać wykonana za pomocą (nieudokumentowanego) astgetconf()interfejsu API. Wewnątrz ksh93The getconfwbudowane (uruchamiana z builtin getconflub powołując command /opt/ast/bin/getconf) jest interfejsemastgetconf()

Na przykład powinieneś builtin getconf; getconf UNIVERSE = attzmienić UNIVERSEustawienie na att(powodując echomiędzy innymi zachowanie SysV). Po wykonaniu tej czynności zauważysz, że $_AST_FEATURESzmienna środowiskowa zawiera UNIVERSE = att.

Stéphane Chazelas
źródło
13
Wiele wczesnych prac nad Uniksem wydarzyło się w oderwaniu, a dobre zasady inżynierii oprogramowania, takie jak „po zmianie interfejsu, zmianie nazwy” nie zostały zastosowane.
Henk Langeveld
7
Uwaga: jedyną (i być może jedyną) zaletą echorozszerzenia \xsekwencji w przeciwieństwie do powłoki jako części składni cytowania jest to, że możesz następnie wyprowadzić bajt NUL (innym prawdopodobnie unikatowym błędnym zaprojektowaniem Uniksa są te rozdzielone zerami ciągi, w których połowa wywołań systemowych (jak execve()) nie może przyjmować dowolnych sekwencji bajtów)
Stéphane Chazelas
Jak możesz zobaczyć na stronie internetowej Svena Mascheka, nawet printfwydaje się być problemem, ponieważ większość implementacji robi to źle. Jedyna poprawna implementacja, o której wiem, jest dostępna, bosha strona Sven Maschek nie wymienia problemów z bajtami zerowymi za pośrednictwem \0.
schily,
1
To świetna odpowiedź - dzięki @ StéphaneChazelas za napisanie tego.
JoshuaRLi
28

Możesz użyć printfjego opcji formatowania. echojest przydatny, jeśli chodzi o drukowanie wartości zmiennej lub (prostej) linii, ale to wszystko. printfpotrafi w zasadzie robić to, co potrafi wersja C.

Przykładowe użycie i możliwości:

Echo:

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

printf:

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

Źródła:

NlightNFotis
źródło
@ 0xC0000022L Stoję za poprawione dzięki. Nie spostrzegłem, że w pośpiechu podałem link do niewłaściwej strony, aby odpowiedzieć na pytanie. Dziękujemy za Twój wkład i korektę.
NlightNFotis
7
Użycie echodo wydrukowania zmiennej może się nie powieść, jeśli wartość zmiennej zawiera metaznaki.
Keith Thompson
17

Jedną z „zalet”, jeśli chcesz to tak nazwać, jest to, że nie musisz mówić, echoaby interpretował pewne sekwencje specjalne, takie jak \n. Potrafi je interpretować i nie będzie tego wymagać -e.

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

(Uwaga: ostatni \njest konieczny, echoimplikuje to, chyba że dasz -nopcję)

przeciw

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

Uwaga ostatni \nw printf. Na koniec dnia zależy od gustu i wymagań, jakich używasz: echolub printf.

0xC0000022L
źródło
1
Prawda /usr/bin/echoi bashwbudowane. dash, kshA zshwbudowana echonie wymaga -eprzełącznika rozszerzyć znaki odwróconego ukośnika.
manatwork
Dlaczego przerażają cytaty? Twoje sformułowanie sugeruje, że niekoniecznie jest to prawdziwa zaleta.
Keith Thompson
2
@KeithThompson: właściwie wszystkie są rozumie się sugerować, że nie każdy może to zaleta rozważenia.
0xC0000022L
Mógłbyś to rozwinąć? Dlaczego nie miałoby to być zaletą? Wyrażenie „jeśli chcesz to tak nazwać”, całkiem mocno sugeruje, że tak nie jest.
Keith Thompson
2
Lub możesz po prostu zrobić printf '%s\n' 'foo\bar.
nyuszika7h
6

Wadą printfjest wydajność, ponieważ wbudowana powłoka echojest znacznie szybsza. Dotyczy to szczególnie Cygwin, gdzie każde wystąpienie nowego polecenia powoduje duże obciążenie systemu Windows. Kiedy zmieniłem mój program z dużym echem na używanie /bin/echoecha powłoki, wydajność prawie się podwoiła. Jest to kompromis między przenośnością a wydajnością. Zawsze nie jest to slam wsadowy printf.

Jan
źródło
15
printfjest obecnie wbudowany w większość powłok (bash, dash, ksh, zsh, yash, niektóre pochodne pdksh ... więc obejmuje również powłoki zwykle spotykane w cygwin). Jedynymi godnymi uwagi wyjątkami są niektóre pdkshinstrumenty pochodne.
Stéphane Chazelas
Ale wiele z tych implementacji printf jest zepsutych. Jest to niezbędne, gdy chcesz użyć printfdo wyprowadzania nul bajtów, ale niektóre implementacje interpretują `\ c 'w ciągu formatu, nawet jeśli nie powinny.
schily,
2
@schily zachowanie printfPOSIX jest nieokreślone, jeśli używasz \cciągu formatu, więc printfimplementacje mogą robić, co chcą w tym względzie. Na przykład niektórzy traktują to tak samo jak w PWB echo(powoduje, że printf kończy działanie), ksh używa go do \cAznaków kontrolnych (dla argumentu formatu printf i dla, $'...'ale nie dla echoani print!). Nie wiesz, co to ma do czynienia z nadrukiem NUL bajtów, a może jesteś odnosząc się do ksh„s printf '\c@'?
Stéphane Chazelas,