it-swarm.com.de

Warum ist printf besser als echo?

Ich habe gehört, dass printf besser ist als echo. Ich kann mich aus meiner Erfahrung nur an eine Instanz erinnern, in der ich printf verwenden musste, weil echo nicht zum Einspeisen von Text in ein Programm unter RHEL 5.8 funktionierte, aber printf. Aber anscheinend gibt es noch andere Unterschiede, und ich möchte nachfragen, was sie sind und ob es bestimmte Fälle gibt, in denen einer gegen den anderen verwendet werden muss.

563
amphibient

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

Anfangs akzeptierte echo keine Option und erweiterte nichts. Alles, was es tat, war, seine Argumente auszugeben, die durch ein Leerzeichen getrennt und durch ein Zeilenumbruchzeichen abgeschlossen waren.

Nun dachte jemand, es wäre schön, wenn wir Dinge wie echo "\n\t" tun könnten, um Zeilenumbrüche oder Tabulatorzeichen auszugeben, oder die Option hätten, das nachfolgende Zeilenumbruchzeichen nicht auszugeben.

Sie überlegten dann genauer, aber anstatt diese Funktionalität zur Shell hinzuzufügen (wie Perl, wo \t in doppelten Anführungszeichen tatsächlich ein Tabulatorzeichen bedeutet), fügten sie sie echo hinzu.

David Korn erkannte den Fehler und führte eine neue Form von Shell-Zitaten ein: $'...', die später von bash und zsh kopiert wurde, aber zu diesem Zeitpunkt war es viel zu spät.

Wenn nun ein Standard-UNIX echo ein Argument empfängt, das die beiden Zeichen \ und t enthält, wird anstelle der Ausgabe ein Tabulatorzeichen ausgegeben. Und sobald \c in einem Argument angezeigt wird, wird die Ausgabe beendet (sodass auch die nachfolgende neue Zeile nicht ausgegeben wird).

Andere Shells/Unix-Anbieter/-Versionen haben sich für eine andere Vorgehensweise entschieden: Sie haben eine Option -e hinzugefügt, um Escape-Sequenzen zu erweitern, und eine Option -n, um die nachfolgende Newline nicht auszugeben. Einige haben einen -E, um Escape-Sequenzen zu deaktivieren, andere haben -n, aber nicht -e. Die Liste der Escape-Sequenzen, die von einer echo-Implementierung unterstützt werden, ist nicht unbedingt dieselbe wie die, die von einer anderen unterstützt wird.

Sven Mascheck hat ein Schöne Seite, die das Ausmaß des Problems zeigt .

Bei diesen echo-Implementierungen, die Optionen unterstützen, wird im Allgemeinen kein -- unterstützt, um das Ende von Optionen zu markieren (das echo, das in einigen nicht Bourne-ähnlichen Shells integriert ist, und zsh unterstützt dafür -), so dass es beispielsweise schwierig ist "-n" mit echo in vielen Shells auszugeben.

Bei einigen Shells wie bash¹ oder ksh93² oder yash ($ECHO_STYLE Variable) 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 sich $POSIXLY_CORRECT in der Umgebung und mit der Version befindet4, zsh mit der Option bsd_echo, einige pdksh-basiert mit der Option posix oder ob sie als sh bezeichnet werden oder nicht). Es ist also nicht garantiert, dass sich zwei bashechos, selbst aus derselben Version von bash, gleich verhalten.

POSIX sagt: Wenn das erste Argument -n ist oder ein Argument Backslashes enthält, ist das Verhalten nicht spezifiziert . bash Echo in dieser Hinsicht ist nicht POSIX, da beispielsweise echo -e-e<newline> nicht ausgibt, wie es POSIX erfordert. Die UNIX-Spezifikation ist strenger, sie verbietet -n und erfordert die Erweiterung einiger Escape-Sequenzen, einschließlich der \c, um die Ausgabe zu beenden.

Diese Spezifikationen helfen hier nicht wirklich, da viele Implementierungen nicht konform sind. Sogar einige zertifizierte Systeme wie macOS5 sind nicht konform.

Um die aktuelle Realität wirklich darzustellen, POSIX sollte eigentlich sagenwenn das erste Argument mit dem erweiterten regulären Ausdruck ^-([eEn]*|-help|-version)$ übereinstimmt oder ein Argument Backslashes enthält (oder Zeichen, deren Codierung die Codierung des Backslash-Zeichens wie α in Gebietsschemas mit dem BIG5-Zeichensatz enthält), ist das Verhalten nicht angegeben.

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

Das bedeutet also, dass Sie echo nicht zum Anzeigen unkontrollierter Daten verwenden können. Mit anderen Worten, wenn Sie ein Skript schreiben und externe Eingaben (vom Benutzer als Argumente oder Dateinamen vom Dateisystem ...) vornehmen, können Sie echo nicht zum Anzeigen verwenden.

Das ist in Ordnung:

echo >&2 Invalid file.

Das ist nicht:

echo >&2 "Invalid file: $file"

(Obwohl es mit einigen (nicht UNIX-kompatiblen) echo-Implementierungen wie bash funktioniert, wenn die Option xpg_echo nicht auf die eine oder andere Weise wie zum Zeitpunkt der Kompilierung oder über die Umgebung aktiviert wurde).

file=$(echo "$var" | tr ' ' _) ist in den meisten Implementierungen nicht in Ordnung (Ausnahmen sind yash mit ECHO_STYLE=raw (mit der Einschränkung, dass die Variablen von yash keine beliebigen Folgen von Bytes enthalten können, also keine beliebigen Dateinamen) und zsh 's echo -E - "$var"6).

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

printf '%s\n' "$var"

Gibt den Inhalt von $var gefolgt von einem Zeilenumbruchzeichen aus, unabhängig davon, welches Zeichen es enthalten kann.

printf '%s' "$var"

Gibt es ohne das nachfolgende Zeilenumbruchzeichen aus.

Jetzt gibt es auch Unterschiede zwischen printf-Implementierungen. Es gibt einen Kern von Funktionen, die von POSIX spezifiziert werden, aber dann gibt es viele Erweiterungen. Einige unterstützen beispielsweise einen %q, um die Argumente zu zitieren, aber wie dies gemacht wird, variiert von Shell zu Shell, andere unterstützen \uxxxx für Unicode-Zeichen. Das Verhalten variiert für printf '%10s\n' "$var" in Multi-Byte-Gebietsschemas. Für printf %b '\123' gibt es mindestens drei verschiedene Ergebnisse

Aber am Ende haben Sie keine Probleme, wenn Sie sich an die POSIX-Funktionen von printf halten und nicht versuchen, etwas Besonderes zu tun.

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

Ein zuverlässigeres echo kann mit printf implementiert werden, wie:

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 (was in den meisten Shell-Implementierungen das Laichen eines zusätzlichen Prozesses impliziert) kann vermieden werden, indem local IFS mit vielen Shells verwendet wird oder indem Folgendes geschrieben wird:

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

Anmerkungen

1. wie das bash-Verhalten von echo geändert werden kann.

Mit bash gibt es zur Laufzeit zwei Dinge, die das Verhalten von echo steuern (neben enable -n echo oder die Neudefinition von echo als Funktion oder Alias): die Option xpg_echobash und ob sich bash im Posix-Modus befindet. Der posix -Modus kann aktiviert werden, wenn bash als sh aufgerufen wird oder wenn sich POSIXLY_CORRECT in der Umgebung befindet oder mit der Option posix:

Standardverhalten auf den meisten Systemen:

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

xpg_echo erweitert Sequenzen nach UNIX:

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

Es werden weiterhin -n und -e (und -E) gewürdigt:

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

Mit xpg_echo und 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 ist bash sowohl POSIX- als auch UNIX-konform. Beachten Sie, dass bash im POSIX-Modus immer noch nicht POSIX-konform ist, da -e nicht ausgegeben wird in:

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

$

Die Standardwerte für xpg_echo und posix können zur Kompilierungszeit mit den Optionen --enable-xpg-echo-default und --enable-strict-posix-default für das Skript configure definiert werden. Dies ist normalerweise das, was neuere Versionen von OS/X tun, um ihren /bin/sh zu erstellen. Keine vernünftige Unix/Linux-Implementierung/-Distribution würde dies normalerweise für /bin/bash tun. Tatsächlich stimmt das nicht. Der /bin/bash, den Oracle mit Solaris 11 (in einem optionalen Paket) liefert, scheint mit --enable-xpg-echo-default erstellt zu sein (was in Solaris 10 nicht der Fall war).

2. Wie das echo-Verhalten von ksh93 geändert werden kann.

In ksh93 hängt es vom Inhalt der Umgebungsvariablen $PATH und/oder $_AST_FEATURES ab, ob echo Escape-Sequenzen erweitert oder nicht und Optionen erkennt.

Wenn $PATH eine Komponente enthält, die /5bin oder /xpg vor der Komponente /bin oder /usr/bin enthält, verhält sie sich wie SysV/UNIX (erweitert Sequenzen, akzeptiert keine Optionen). Wenn zuerst /ucb oder /bsd gefunden wird oder wenn $_AST_FEATURES7 enthält UNIVERSE = ucb, dann verhält es sich wie das BSD3 Weg (-e, um die Erweiterung zu aktivieren, erkennt -n).

Die Standardeinstellung ist systemabhängig, BSD von Debian (siehe die Ausgabe von builtin getconf; getconf UNIVERSE in 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 Verweis auf BSD für die Behandlung der Option -e ist hier etwas irreführend. Die meisten dieser unterschiedlichen und inkompatiblen echo Verhaltensweisen wurden bei AT & T eingeführt:

  • \n, \0ooo, \c in Programmer's Work Bench UNIX (basierend auf Unix V6) und der Rest (\b, \r...) in Unix System IIIRef.
  • -n in Unix V7 (von Dennis RitchieRef)
  • -e in Unix V8 (von Dennis RitchieRef)
  • -E selbst stammt möglicherweise ursprünglich von bash (CWRU/CWRU.chlog in Version 1.13.5 erwähnt, dass Brian Fox es am 18.10.1992 hinzugefügt hat, GNU echo kopiert es kurz danach in sh-utils-1.8 10 Tage später veröffentlicht)

Während das echo, das in sh von BSDs integriert ist, -e seit dem Tag unterstützt, an dem die Almquist Shell in den frühen 90er Jahren verwendet wurde, wird es vom eigenständigen Dienstprogramm echo bis heute dort nicht unterstützt ( FreeBSD echo = unterstützt -e immer noch nicht, obwohl es -n wie Unix V7 unterstützt (und auch \c, aber nur am Ende des letzten Arguments)).

Die Behandlung von -e wurde ksh93s echo im BSD-Universum in der 2006 veröffentlichten ksh93r-Version hinzugefügt und kann zum Zeitpunkt der Kompilierung deaktiviert werden.

4. GNU Echo Verhaltensänderung in 8.31

Seit coreutils 8.31 (und this commit ) erweitert GNU echo jetzt standardmäßig Escape-Sequenzen, wenn sich POSIXLY_CORRECT in der Umgebung befindet, um dem Verhalten von bash -o posix -O xpg_echos echo zu entsprechen (siehe Fehlerbericht ).

5. macOS echo

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

Ihr sh-integriertes echo ist kompatibel, da es bash (eine sehr alte Version) ist, für die xpg_echo standardmäßig aktiviert ist, ihr eigenständiges echo-Dienstprogramm jedoch nicht. env echo -n gibt nichts anstelle von -n<newline> aus, env echo '\n' gibt \n<newline> anstelle von <newline><newline> aus.

Dieser /bin/echo ist der von FreeBSD, der die Newline-Ausgabe unterdrückt, wenn das erste Argument -n ist oder (seit 1995), wenn das letzte Argument mit \c endet, aber keine anderen von UNIX geforderten Backslash-Sequenzen unterstützt, nicht einmal \\.

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

Genau genommen können Sie auch zählen, dass FreeBSD/macOS /bin/echo oben (nicht der integrierte echo der Shell), in dem zshs echo -E - "$var" oder yashs ECHO_STYLE=raw echo "$var" (printf '%s\n' "$var") geschrieben werden könnte:

/bin/echo "$var
\c"

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

/bin/echo "$var\c"

Implementierungen, die -E und -n unterstützen (oder für die konfiguriert werden können), können auch Folgendes tun:

echo -nE "$var
"

Für das Äquivalent von printf '%s\n' "$var".

7. _AST_FEATURES und das AST UNIVERSE

Der _AST_FEATURES soll nicht direkt manipuliert werden, sondern wird verwendet, um AST Konfigurationseinstellungen über die Befehlsausführung zu verbreiten. Die Konfiguration soll über die (undokumentierte) astgetconf() API erfolgen. In ksh93 ist das integrierte getconf (aktiviert mit builtin getconf oder durch Aufrufen von command /opt/ast/bin/getconf) die Schnittstelle zu astgetconf()

Zum Beispiel würden Sie builtin getconf; getconf UNIVERSE = att ausführen, um die Einstellung UNIVERSE in att zu ändern (was dazu führt, dass sich echo unter anderem wie SysV verhält). Danach werden Sie feststellen, dass die Umgebungsvariable $_AST_FEATURESUNIVERSE = att enthält.

785

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

Anwendungsbeispiel und Funktionen:

Echo:

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

printf:

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

Quellen:

28
NlightNFotis

Ein "Vorteil", wenn Sie es so nennen möchten, wäre, dass Sie es nicht wie echo sagen müssen, um bestimmte Escape-Sequenzen wie \n Zu interpretieren. Es kann sie interpretieren und benötigt dazu keinen -e.

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

(Hinweis: Das letzte \n Ist erforderlich. echo impliziert dies, es sei denn, Sie geben die Option -n An.)

versus

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

Beachten Sie das letzte \n In printf. Am Ende des Tages ist es eine Frage des Geschmacks und der Anforderungen , was Sie verwenden: echo oder - printf .

17
0xC0000022L

Ein Nachteil von printf ist die Leistung, da die integrierte Shell echo viel schneller ist. Dies kommt insbesondere in Cygwin zum Tragen, wo jede Instanz eines neuen Befehls einen hohen Windows-Overhead verursacht. Als ich mein echolastiges Programm von /bin/echo Auf das Echo der Shell umstellte, verdoppelte sich die Leistung fast. Es ist ein Kompromiss zwischen Portabilität und Leistung. Es ist kein Slam Dunk, immer printf zu verwenden.

5
John