Warum ist printf besser als echo?

547

Ich habe gehört, das printfist besser als echo. Ich kann mich aus meiner Erfahrung nur an eine Instanz erinnern, die ich verwenden musste, printfweil echosie nicht zum Einfügen von Text in ein Programm unter RHEL 5.8 printffunktioniert hat, sondern . Aber anscheinend gibt es andere Unterschiede, und ich möchte nachfragen, was sie sind und ob es bestimmte Fälle gibt, in denen man einen gegen den anderen verwendet.

amphibient
quelle
Was ist los echo -e?
neverMind9
1
@ neverMind9 echo -efunktioniert nicht in sh, nur in bash(aus irgendeinem Grund), und Docker verwendet beispielsweise shstandardmäßig für seine RUNBefehle
Ciprian Tomoiagă
@ CiprianTomoiagă funktioniert echo -efür mich im Android Terminal, was im Wesentlichen ist sh.
neverMind9

Antworten:

756

Grundsätzlich handelt es sich um eine Frage der Portabilität (und Zuverlässigkeit).

Anfangs echohabe ich keine Option akzeptiert und nichts erweitert. Alles, was es tat, war die Ausgabe seiner Argumente, die durch ein Leerzeichen getrennt und durch ein Zeilenvorschubzeichen abgeschlossen wurden.

Nun dachte jemand, es wäre schön, wenn wir Dinge tun könnten, um echo "\n\t"Zeilenvorschub- oder Tabulatorzeichen auszugeben oder die Option zu haben, das abschließende Zeilenvorschubzeichen nicht auszugeben.

Sie dachten dann viel genauer nach, fügten diese Funktionalität jedoch nicht der Shell hinzu (beispielsweise, perlwenn in doppelten Anführungszeichen \tein Tabulatorzeichen steht) echo.

David Korn realisiert den Fehler und stellte eine neue Form der Schale Zitate: $'...'die durch später kopiert wurde bashund zshaber es war viel zu spät von dieser Zeit.

Wenn nun ein Standard-UNIX echoein Argument empfängt, das die beiden Zeichen enthält, \und tstatt sie auszugeben, gibt es ein Tabulatorzeichen aus. Und sobald \cein Argument angezeigt wird, wird die Ausgabe gestoppt (sodass auch die nachgestellte Zeile nicht ausgegeben wird).

Andere Shells / Unix-Anbieter / -Versionen haben sich für eine andere Vorgehensweise entschieden: Sie haben eine -eOption zum Erweitern von Escape-Sequenzen und eine -nOption hinzugefügt , mit der die nachgestellte Zeile nicht ausgegeben wird. Einige müssen -EEscape-Sequenzen deaktivieren, andere -njedoch nicht -e. Die Liste der von einer echoImplementierung unterstützten Escape-Sequenzen muss nicht mit der Liste der von einer anderen Implementierung unterstützten identisch sein.

Sven Mascheck hat eine schöne Seite, die das Ausmaß des Problems zeigt .

Bei echoImplementierungen, die Optionen unterstützen, gibt es im Allgemeinen keine Unterstützung für a --, um das Ende von Optionen zu markieren ( echodies ist bei einigen nicht Bourne-ähnlichen Shells der Fall, und zsh unterstützt dies -jedoch). Daher ist es beispielsweise schwierig, "-n"mit echoin auszugeben viele Muscheln.

Bei einigen Shells wie bash¹ oder ksh93² oder yash( $ECHO_STYLEvariabel) hängt das Verhalten sogar davon ab, wie die Shell kompiliert wurde oder von der Umgebung (das Verhalten von GNU echoändert sich auch, wenn $POSIXLY_CORRECTes sich in der Umgebung befindet und mit der Version 4 , zshmit der bsd_echoOption, einige pdksh-basierte mit ihrer posixOption oder ob sie als genannt werden shoder nicht). Es ist also nicht garantiert, dass sich zwei bash echos, auch von derselben Version von bash, gleich verhalten.

POSIX sagt: Wenn das erste Argument -nBackslashes enthält, ist das Verhalten nicht spezifiziert . bashEcho in dieser Hinsicht ist nicht POSIX, dh es echo -ewird nicht so ausgegeben, -e<newline>wie es POSIX erfordert. Die UNIX-Spezifikation ist strenger, sie verbietet -nund erfordert die Erweiterung einiger Escape-Sequenzen, einschließlich \cderjenigen, um die Ausgabe zu stoppen.

Diese Spezifikationen können hier nicht wirklich Abhilfe schaffen, da viele Implementierungen nicht konform sind. Sogar einige zertifizierte Systeme wie MacOS 5 sind nicht kompatibel.

Um die aktuelle Realität wirklich darzustellen, sollte POSIX tatsächlich sagen : Wenn das erste Argument mit dem ^-([eEn]*|-help|-version)$erweiterten regulären Ausdruck übereinstimmt oder ein Argument umgekehrte Schrägstriche enthält (oder Zeichen, deren Codierung die Codierung des umgekehrten Schrägstrichs enthält, wie αin Gebietsschemas unter Verwendung des BIG5-Zeichensatzes), ist das Verhalten wie folgt nicht spezifiziert.

Alles in allem wissen Sie nicht, was echo "$var"ausgegeben wird, es sei denn, Sie können sicherstellen, dass $vardie Ausgabe keine Backslash-Zeichen enthält und nicht mit beginnt -. Die POSIX-Spezifikation sagt uns tatsächlich, dass wir printfin diesem Fall stattdessen verwenden sollen.

Das bedeutet also, dass Sie keine echounkontrollierten Daten anzeigen können. Mit anderen Worten, wenn Sie ein Skript schreiben und externe Eingaben (vom Benutzer als Argumente oder Dateinamen aus dem Dateisystem ...) verwendet werden, können Sie es nicht echozum Anzeigen verwenden.

Das ist in Ordnung:

echo >&2 Invalid file.

Das ist nicht:

echo >&2 "Invalid file: $file"

(Obwohl es bei einigen (nicht UNIX-kompatiblen) echoImplementierungen wie bash's einwandfrei funktioniert , wenn die xpg_echoOption nicht auf die eine oder andere Weise aktiviert wurde, wie zum Zeitpunkt der Kompilierung oder über die Umgebung).

file=$(echo "$var" | tr ' ' _)nicht OK in den meisten Implementierungen ist (Ausnahmen sind yashmit ECHO_STYLE=raw(mit dem Vorbehalt , dass yash‚s Variablen nicht beliebige Sequenzen von Bytes so nicht willkürlich Dateinamen) und halten kann zshs‘ echo -E - "$var"6 ).

printfAndererseits ist es zuverlässiger, zumindest wenn es auf die grundlegende Verwendung von beschränkt ist echo.

printf '%s\n' "$var"

$varGibt den Inhalt von gefolgt von einem Newline-Zeichen aus, unabhängig davon, welches Zeichen es enthalten darf.

printf '%s' "$var"

Gibt es ohne das nachfolgende Zeilenumbruchzeichen aus.

Nun gibt es auch Unterschiede zwischen den printfImplementierungen. Es gibt einen Kern von Funktionen, die von POSIX spezifiziert werden, aber es gibt auch viele Erweiterungen. Einige unterstützen beispielsweise a %q, um die Argumente in Anführungszeichen zu setzen, aber die Vorgehensweise ist von Shell zu Shell unterschiedlich. Einige unterstützen \uxxxxUnicode-Zeichen. Das Verhalten ist printf '%10s\n' "$var"bei Multi-Byte-Gebietsschemas unterschiedlich. Für gibt es mindestens drei verschiedene Ergebnisseprintf %b '\123'

Aber am Ende haben Sie printfkeine Probleme , wenn Sie sich an die POSIX-Funktionen halten und nicht versuchen, etwas Besonderes daraus zu machen.

Denken Sie jedoch daran, dass das erste Argument das Format ist und daher keine variablen / unkontrollierten Daten enthalten sollte.

Eine zuverlässigere echokann implementiert werden mit 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' "$*"
)

Die Subshell (die in den meisten Shell-Implementierungen das Erzeugen eines zusätzlichen Prozesses impliziert) kann bei Verwendung local IFSmit vielen Shells oder durch Schreiben wie folgt vermieden werden :

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

Anmerkungen

1. Wie ist bashdas echoVerhalten kann geändert werden.

Mit bashzur Laufzeit gibt es zwei Dinge, die das Verhalten steuern echo(neben enable -n echooder neu zu definieren echoals eine Funktion oder Alias): die xpg_echo bashOption , und ob bashim Posix - Modus. posixModus kann aktiviert werden, wenn bashaufgerufen wird als shoder wenn POSIXLY_CORRECTin der Umgebung oder mit der posixOption:

Standardverhalten auf den meisten Systemen:

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

xpg_echo Erweitert Sequenzen entsprechend den Anforderungen von UNIX:

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

Es ehrt noch -nund -e(und -E):

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

Mit xpg_echound POSIX-Modus:

$ 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

Dieses Mal bashist sowohl POSIX- als auch UNIX-konform. Beachten Sie, dass im POSIX-Modus bashimmer noch keine POSIX-Konformität besteht, da die Ausgabe -ein folgenden Sprachen nicht erfolgt :

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

$

Die Standardwerte für xpg_echo und Posix können mit den bei der Kompilierung definiert werden --enable-xpg-echo-defaultund --enable-strict-posix-defaultOptionen zum configureSkript. Dies ist in der Regel das, was neuere Versionen von OS / X tun, um sie zu erstellen /bin/sh. Normalerweise würde dies jedoch keine Unix / Linux-Implementierung / -Distribution tun, die bei /bin/bashklarem Verstand wäre . Eigentlich stimmt das nicht, das /bin/bashOracle mit Solaris 11 (in einem optionalen Paket) ausgeliefert wird --enable-xpg-echo-default(das war in Solaris 10 nicht der Fall).

2. Wie ksh93ist das echoVerhalten verändert werden.

In ksh93, ob echoEscape - Sequenzen erweitert oder nicht und erkennt Optionen ist abhängig von dem Inhalt der $PATHund / oder $_AST_FEATURESUmgebungsvariablen.

Wenn $PATHeine Komponente enthalten ist, die /5binoder /xpgvor der /binoder /usr/bin-Komponente enthält , verhält sie sich wie SysV / UNIX (erweitert Sequenzen, akzeptiert keine Optionen). Findet es /ucboder /bsdzuerst oder enthält es $_AST_FEATURES7UNIVERSE = ucb , dann verhält es sich wie BSD 3 ( -eum die Erweiterung zu ermöglichen, erkennt es -n).

Die Standardeinstellung ist systemabhängig, BSD unter Debian (siehe die Ausgabe builtin getconf; getconf UNIVERSEin neueren Versionen von 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 für Echo -e?

Der Hinweis auf BSD für den Umgang mit der -eOption ist hier etwas irreführend. Die meisten dieser unterschiedlichen und inkompatiblen echoVerhaltensweisen wurden bei AT & T eingeführt:

  • \n, \0ooo, \cIn Programmierwerkbank UNIX (basierend auf Unix V6) und den Rest ( \b, \r...) in Unix System III Ref .
  • -nin Unix V7 (von Dennis Ritchie Ref )
  • -ein Unix V8 (von Dennis Ritchie Ref )
  • -Eselbst möglicherweise ursprünglich von bash(CWRU / CWRU.chlog in Version 1.13.5 erwähnt, dass Brian Fox es am 18.10.1992 hinzufügte, GNU echokopierte es kurz darauf in sh-utils-1.8, das 10 Tage später veröffentlicht wurde)

Während die echobuiltin des shoder BSDs unterstützt hat , -eseit dem Tag , sie es in den frühen 90er Jahren mit dem Almquist Shell gestartet, die Standalone echonicht Dienstprogramm bis heute nicht dort unterstützen ( FreeBSDecho noch nicht unterstützt -e, obwohl es tut Unterstützung -nwie Unix V7 (und auch \cnur am Ende des letzten Arguments).

Die Handhabung -ewurde hinzugefügt ksh93ist , echowenn in dem BSD - Universum in der ksh93r Version im Jahr 2006 veröffentlicht und kann bei der Kompilierung deaktiviert werden.

4. Verhaltensänderung des GNU-Echos in 8.31

Da coreutils 8,31 (und diese begehen ), GNU echoerweitert nun Sequenzen durch Standard entkommen , wenn POSIXLY_CORRECT in der Umwelt ist, das Verhalten anzupassen bash -o posix -O xpg_echo‚s echobuiltin (siehe Bug - Report ).

5. macOS echo

Die meisten Versionen von macOS haben eine UNIX-Zertifizierung von der OpenGroup erhalten .

Ihr sheingebautes System echoist kompatibel, da es bash(eine sehr alte Version) xpg_echostandardmäßig aktiviert ist, ihr eigenständiges echoDienstprogramm jedoch nicht. env echo -ngibt nichts statt aus -n<newline>, env echo '\n'gibt \n<newline>statt aus <newline><newline>.

Das /bin/echoist der von FreeBSD , den Neuen - Zeile - Ausgang unterdrückt , wenn das erste Argumente -noder (seit 1995) , wenn das letzte Argument endet \c, aber unterstützt keine andere Backslash Sequenzen von UNIX erforderlich, auch nicht \\.

6. echoImplementierungen, die beliebige Daten wörtlich ausgeben können

Genau genommen könnte man auch zählen, dass FreeBSD / macOS /bin/echoüber (nicht die echoeingebaute Shell ) wo zsh's echo -E - "$var"oder yash' s ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") geschrieben werden könnte:

/bin/echo "$var
\c"

Implementierungen, die Folgendes unterstützen -Eund -nfür die Folgendes konfiguriert werden kann:

echo -nE "$var
"

Und zsh's echo -nE - "$var"( printf %s "$var") könnte geschrieben werden

/bin/echo "$var\c"

7. _AST_FEATURESund der ASTUNIVERSE

Das _AST_FEATURESist nicht dazu gedacht, direkt manipuliert zu werden. Es wird verwendet, um AST-Konfigurationseinstellungen über die Befehlsausführung zu verbreiten. Die Konfiguration soll über die (undokumentierte) astgetconf()API erfolgen. Im Inneren ksh93ist das getconfeingebaute (mit builtin getconfoder durch Aufrufen aktiviert command /opt/ast/bin/getconf) die Schnittstelle zuastgetconf()

Beispielsweise müssen Sie builtin getconf; getconf UNIVERSE = attdie UNIVERSEEinstellung auf ändern att( echounter anderem , um das SysV-Verhalten zu beeinflussen). Danach werden Sie feststellen, dass die $_AST_FEATURESUmgebungsvariable enthält UNIVERSE = att.

Stéphane Chazelas
quelle
13
Viele frühe Unix-Entwicklungen erfolgten isoliert, und gute Prinzipien der Softwareentwicklung wie "Wenn Sie die Benutzeroberfläche ändern, ändern Sie den Namen" wurden nicht angewendet.
Henk Langeveld
7
Hinweis: Der einzige (und vielleicht einzige) Vorteil der echoErweiterung der \xSequenzen im Gegensatz zur Shell als Teil der Quotierungssyntax besteht darin, dass Sie dann ein NUL-Byte ausgeben können (ein weiteres, möglicherweise falsch definiertes Unix-Byte besteht aus durch Nullen getrennten Sequenzen) Zeichenfolgen, bei denen die Hälfte der Systemaufrufe (wie execve()) keine willkürlichen Folgen von Bytes annehmen kann)
Stéphane Chazelas
Wie Sie vielleicht auf der Webseite von Sven Maschek sehen, printfscheint dies sogar ein Problem zu sein, da die meisten Implementierungen es falsch machen. Die einzige korrekte Implementierung, die mir bekannt ist, ist in boshund auf der Seite von Sven Maschek werden die Probleme mit Null-Bytes über nicht aufgelistet \0.
Schily
1
Dies ist eine großartige Antwort - danke @ StéphaneChazelas für das Schreiben.
JoshuaRLi
28

Möglicherweise möchten Sie printffür die Formatierungsoptionen verwenden. echoist nützlich, wenn es darum geht, den Wert einer Variablen oder einer (einfachen) Zeile zu drucken, aber das ist alles, was dazu gehört. printfkann im Grunde das tun, was die C-Version davon kann.

Anwendungsbeispiele und Funktionen:

Echo:

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

printf:

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

Quellen:

NlightNFotis
quelle
@ 0xC0000022L Ich stehe korrigiert danke. Ich habe nicht bemerkt, dass ich in meinem Ansturm auf die Beantwortung der Frage auf die falsche Seite gelinkt bin. Vielen Dank für Ihren Beitrag und die Korrektur.
NlightNFotis
7
Das echoDrucken einer Variablen kann fehlschlagen, wenn der Wert der Variablen Metazeichen enthält.
Keith Thompson
17

Ein "Vorteil", wenn man es so nennen will, wäre, dass man es nicht so sagen muss echo, um bestimmte Escape-Sequenzen wie z \n. Es weiß, wie man sie interpretiert, und es wird nicht erforderlich sein -e, dies zu tun.

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

(NB: der letzte \nist notwendig, echoimpliziert es, wenn Sie die -nOption geben)

gegen

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

Beachten Sie die letzte \nin printf. Am Ende des Tages ist es eine Frage des Geschmacks und der Anforderungen, was Sie verwenden: echooder printf.

0xC0000022L
quelle
1
Zutreffend für /usr/bin/echound das basheingebaute. Der Schalter dash, kshund zshbuiltin echomuss nicht verwendet -ewerden, um Zeichen mit umgekehrten Schrägstrichen zu erweitern.
Manatwork
Warum zitiert die Angst? Ihre Formulierung impliziert, dass dies nicht unbedingt ein echter Vorteil ist.
Keith Thompson
2
@KeithThompson: eigentlich alles , was sie gemeint bedeuten, ist , dass nicht jeder es ein Vorteil betrachten kann.
0xC0000022L
Könntest du das weiter erläutern? Warum wäre es kein Vorteil? Der Ausdruck "wenn du es so nennen willst" impliziert ziemlich stark, dass du denkst, dass es nicht so ist.
Keith Thompson
2
Oder du könntest es einfach tun printf '%s\n' 'foo\bar.
nyuszika7h
6

Ein Nachteil printfist die Leistung, da die integrierte Shell echoviel schneller ist. Dies kommt insbesondere in Cygwin zum Tragen, wo jede Instanz eines neuen Befehls einen hohen Windows-Overhead verursacht. Als ich mein Echo-lastiges Programm von "using" /bin/echoauf das Echo der Shell umstellte, verdoppelte sich die Leistung fast. Es ist ein Kompromiss zwischen Portabilität und Leistung. Es ist kein Slam Dunk, den man immer benutzt printf.

John
quelle
15
printfist heutzutage in den meisten Shells enthalten (bash, dash, ksh, zsh, yash, einige pdksh-Derivate ... also auch die für Cygwin typischen Shells). Die einzigen nennenswerten Ausnahmen sind einige pdkshDerivate.
Stéphane Chazelas
Viele dieser printf-Implementierungen sind jedoch fehlerhaft. Dies ist wichtig, wenn Sie printfzur Ausgabe von nul Bytes verwenden möchten, aber einige Implementierungen interpretieren "\ c" in der Formatzeichenfolge, obwohl dies nicht der Fall sein sollte.
Schily
2
@schily, das Verhalten von printfwird von POSIX nicht spezifiziert, wenn Sie \cin der Formatzeichenfolge verwenden , sodass printfImplementierungen diesbezüglich tun können, was sie wollen. Zum Beispiel behandeln einige es genauso wie in der PWB echo(bewirkt, dass printf beendet wird), ksh verwendet es für \cASteuerzeichen (für das printf-Format-Argument und für, $'...'aber nicht für echonoch print!). Nicht sicher, was das mit dem Drucken von NUL-Bytes zu tun hat, oder beziehen Sie sich vielleicht auf ksh's printf '\c@'?
Stéphane Chazelas