Es ist nicht nur Echo gegen Printf
Lassen Sie uns zunächst verstehen, was mit dem read a b cTeil passiert . readführt eine Wortteilung auf der Grundlage des Standardwerts der IFSVariablen 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 bashHandbuch 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.txtTextdateien 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 readwäre , würde ich versuchen, jedes einzelne Wort in eine lineVariable einzupassen . Aber das ist eine andere Geschichte, die Sie später studieren sollten, da while IFS= read -r variablees sich um eine sehr häufig verwendete Struktur handelt.
Echo vs Printf-Verhalten
echotut, was Sie hier erwarten würden. Es zeigt Ihre Variablen genau so an, wie readsie angeordnet wurden. Dies wurde bereits in der vorangegangenen Diskussion gezeigt.
printfist 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 $cprintf 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 $cVariable in Anführungszeichen gesetzt ist, wird sie jetzt als eine ganze Zeichenfolge erkannt 3 4und passt nicht zum %dFormat, 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=execveund 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, bashhat es ein eigenes eingebautes printf, was Sie in Ihrem Befehl verwenden, stracewird tatsächlich die /usr/bin/printfVersion 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 printfSyntax weitaus portabler (und daher vorzuziehen ist) als echodie Syntax von C oder einer C-ähnlichen Sprache, in der printf()Funktionen enthalten sind. Sehen Sie diese ausgezeichnete Antwort von terdon zum Thema printfvs 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 printfEcho 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).
straceFall und dem anderenstrace printfist die Verwendung/usr/bin/printf, währendprintfdirekt in bash die gleichnamige Shell verwendet wird. Sie sind nicht immer identisch. Beispielsweise verfügt die Bash-Instanz über Formatspezifizierer%qund in neuen Versionen$()Tüber eine Zeitformatierung.