Es ist nicht nur Echo gegen Printf
Lassen Sie uns zunächst verstehen, was mit dem read a b c
Teil passiert . read
führt eine Wortteilung auf der Grundlage des Standardwerts der IFS
Variablen aus, nämlich Leerzeichen-Tabulator-Zeilenvorschub, und passt alles auf dieser Grundlage an. Wenn mehr Eingaben als die Variablen vorhanden sind, werden aufgeteilte Teile in die ersten Variablen eingepasst, und was nicht eingepasst werden kann, wird in die letzten eingepasst. Folgendes meine ich:
bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four
Genau so wird es im bash
Handbuch beschrieben (siehe Zitat am Ende der Antwort).
In Ihrem Fall passen 1 und 2 in die Variablen a und b, und c nimmt alles andere, was ist 3 4 5 6
.
Was Sie auch oft sehen werden, ist, dass die Leute while IFS= read -r line; do ... ; done < input.txt
Textdateien zeilenweise lesen. Auch IFS=
hier gibt es einen Grund, die Wortteilung zu steuern, oder genauer gesagt: Deaktivieren Sie sie, und lesen Sie eine einzelne Textzeile in eine Variable. Wenn es nicht da read
wäre , würde ich versuchen, jedes einzelne Wort in eine line
Variable einzupassen . Aber das ist eine andere Geschichte, die Sie später studieren sollten, da while IFS= read -r variable
es sich um eine sehr häufig verwendete Struktur handelt.
Echo vs Printf-Verhalten
echo
tut, was Sie hier erwarten würden. Es zeigt Ihre Variablen genau so an, wie read
sie angeordnet wurden. Dies wurde bereits in der vorangegangenen Diskussion gezeigt.
printf
ist etwas ganz Besonderes, da es weiterhin Variablen in Format-Strings einfügt, bis alle erschöpft sind. Wenn Sie also printf "%d, %d, %d \n" $a $b $c
printf ausführen, wird eine Formatzeichenfolge mit 3 Dezimalstellen angezeigt, es gibt jedoch mehr Argumente als 3 (da Ihre Variablen tatsächlich auf einzelne 1,2,3,4,5,6 erweitert werden). Dies mag verwirrend klingen, existiert aber aus einem Grund als verbessertes Verhalten gegenüber dem, was die eigentliche printf()
Funktion in C-Sprache leistet.
Was Sie auch hier getan haben, was sich auf die Ausgabe auswirkt, ist, dass Ihre Variablen nicht in Anführungszeichen gesetzt sind, wodurch die Shell (nicht printf
) die Variablen in 6 separate Elemente aufteilen kann. Vergleichen Sie dies mit Zitieren:
bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3
Gerade weil $c
Variable in Anführungszeichen gesetzt ist, wird sie jetzt als eine ganze Zeichenfolge erkannt 3 4
und passt nicht zum %d
Format, das nur eine einzelne Ganzzahl ist
Jetzt mache dasselbe ohne zu zitieren:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
Sagt noch einmal: "OK, Sie haben 6 Elemente dort, aber das Format zeigt nur 3 an, also werde ich weiterhin passende Elemente einfügen und alles leer lassen, was ich nicht mit den tatsächlichen Eingaben des Benutzers vergleichen kann."
Und in all diesen Fällen musst du nicht mein Wort dafür nehmen. Laufen Sie einfach strace -e trace=execve
und sehen Sie selbst, was der Befehl tatsächlich "sieht":
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: ‘3 4’: value not completely converted
3
+++ exited with 1 +++
Zusätzliche Bemerkungen
Wie Charles Duffy in den Kommentaren richtig hervorhob, bash
hat es ein eigenes eingebautes printf
, was Sie in Ihrem Befehl verwenden, strace
wird tatsächlich die /usr/bin/printf
Version aufrufen , nicht die Shell-Version. Abgesehen von geringfügigen Unterschieden sind für unser Interesse an dieser speziellen Frage die Standardformatspezifizierer gleich und das Verhalten gleich.
Zu beachten ist auch, dass die printf
Syntax weitaus portabler (und daher vorzuziehen ist) als echo
die Syntax von C oder einer C-ähnlichen Sprache, in der printf()
Funktionen enthalten sind. Sehen Sie diese ausgezeichnete Antwort von terdon zum Thema printf
vs echo
. Sie können die Ausgabe zwar auf Ihre spezifische Shell in Ihrer spezifischen Ubuntu-Version abstimmen, aber wenn Sie Skripte auf verschiedene Systeme portieren möchten, sollten Sie es wahrscheinlich vorziehen, das printf
Echo zu verwenden. Vielleicht sind Sie ein Systemadministrator für Anfänger, der mit Ubuntu- und CentOS-Maschinen arbeitet, oder vielleicht sogar FreeBSD - wer weiß -, also müssen Sie in solchen Fällen Entscheidungen treffen.
Zitat aus Bash-Handbuch, Abschnitt SHELL BUILTIN COMMANDS
read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]
Eine Zeile wird aus der Standardeingabe oder aus dem Dateideskriptor fd gelesen, der als Argument für die Option -u angegeben wurde, und das erste Wort wird dem Vornamen zugewiesen, das zweite Wort dem zweiten Namen und so weiter Wörter und deren dazwischenliegende Trennzeichen, die dem Nachnamen zugeordnet sind. Wenn weniger Wörter als Namen aus dem Eingabestream gelesen werden, werden den verbleibenden Namen leere Werte zugewiesen. Die Zeichen in IFS werden verwendet, um die Zeile nach denselben Regeln in Wörter aufzuteilen, die die Shell für die Erweiterung verwendet (siehe oben unter Wortteilung).
strace
Fall und dem anderenstrace printf
ist die Verwendung/usr/bin/printf
, währendprintf
direkt in bash die gleichnamige Shell verwendet wird. Sie sind nicht immer identisch. Beispielsweise verfügt die Bash-Instanz über Formatspezifizierer%q
und in neuen Versionen$()T
über eine Zeitformatierung.