Wenn Sie "während des Lesens ..." verwenden, erhalten Echo und Ausdruck unterschiedliche Ergebnisse

14

Nach dieser Frage " Using" while read ... "in einem Linux-Skript "

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

Ergebnis:

1, 2, 3 4 5 6

aber wenn ich ersetzen echomitprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

Ergebnis

1, 2, 3
4, 5, 6

Könnte mir bitte jemand sagen, was diese beiden Befehle auszeichnet? Vielen Dank ~

user3094631
quelle

Antworten:

24

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).

Sergiy Kolodyazhnyy
quelle
2
Ein bemerkenswerter Unterschied zwischen dem straceFall und dem anderen strace printfist die Verwendung /usr/bin/printf, während printfdirekt 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.
Charles Duffy
7

Dies ist nur ein Vorschlag und soll Sergijs Antwort überhaupt nicht ersetzen. Ich denke, Sergiy hat eine großartige Antwort darauf geschrieben, warum sie sich im Druck unterscheiden. Wie die Variable beim Lesen mit den verbleibenden in die $cVariable wie 3 4 5 6folgt zugewiesen wird und 1und 2zugewiesen werden . teilt die Variable nicht für Sie auf, wo es mit dem s sein soll.abechoprintf%d

Sie können sie jedoch so einstellen, dass sie Ihnen im Grunde die gleichen Antworten geben, indem Sie das Echo der Zahlen am Anfang des Befehls bearbeiten:

In können /bin/bashSie verwenden:

echo -e "1 2 3 \n4 5 6"

In können /bin/shSie einfach verwenden:

echo "1 2 3 \n4 5 6"

Bash verwendet das -e, um die \ escape-Zeichen zu aktivieren, wenn es von sh nicht benötigt wird, da es bereits aktiviert ist. \nbewirkt, dass eine neue Zeile erstellt wird, sodass die Echozeile jetzt in zwei separate Zeilen aufgeteilt wird, die jetzt zweimal für Ihre Echo-Loop-Anweisung verwendet werden können:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

Was wiederum die gleiche Ausgabe mit dem printfBefehl erzeugt:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Im sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Hoffe das hilft!

Terrance
quelle
echo -eist nicht nur eine Erweiterung von POSIX - sondern jede echo, die nicht -ein ihre Ausgabe eingeht, da die Eingabe tatsächlich die Black-Letter-Spezifikation missachtet. (bash ist standardmäßig nicht kompatibel, entspricht jedoch dem Standard, wenn beide posixund xpg_echoFlags gesetzt sind. Letzteres kann zur Kompilierungszeit als Standard festgelegt werden, was bedeutet, dass nicht alle Versionen von bash so funktionieren, echo -ewie Sie es hier erwarten).
Charles Duffy
In den Abschnitten APPLICATION USAGE und RATIONALE der POSIX-Spezifikation finden Sie echounter pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html eine explizite Beschreibung echodes Verhaltens, das bei einem -noder mehreren Backslashes in der Eingabe nicht angegeben ist printfstattdessen verwendet werden.
Charles Duffy
@CharlesDuffy Habe ich jemals in meinem geschrieben, dass ich davon ausgehe, dass dies in allen Versionen von bash funktioniert? Und welches Problem hast du mit meiner Antwort? Wenn Sie der Meinung sind, dass Sie eine bessere Lösung haben, schreiben Sie diese als Antwort, anstatt zu versuchen, den Menschen das Gegenteil zu beweisen. Ich habe diesen Weg zu oft mit Leuten wie Ihnen durchgemacht, also hören Sie bitte mit diesen Kommentaren auf. Das ist alles was ich zu sagen habe.
Terrance
3
@CharlesDuffy In Ask Ubuntu schreiben wir manchmal Antworten, die nicht auf andere Linux-Distributionen übertragbar sind. Da Ihr Repräsentant hier nur 103 Punkte hat, haben Sie das vielleicht nicht bemerkt. Ihr Mitarbeiter bei Stack Overflow hat jedoch 123,3 KB Punkte, sodass Sie offensichtlich eine Menge über * NIX-Systeme im Allgemeinen wissen. Ich denke, dass Sie, Terrace und Serg in Ordnung sind, aber den Faden aus verschiedenen Blickwinkeln betrachten. Ich gebe das Gefühl wieder (kein Wortspiel beabsichtigt), dass Sie eine Antwort schreiben, die * NIX portabel oder zumindest portabel in Linux-Distributionen ist.
WinEunuuchs2Unix
2
@CharlesDuffy Während ich stimme mit Ihrem Gefühl Antwort sollten tragbar gemacht werden, dann ist dies immerhin Ubuntu-orientierte Website, damit die Ubuntu-spezifischen Tools von den Nutzern angenommen werden sollte. Die Warnung ist also etwas implizit. Lassen Sie uns nicht zu lange diskutieren. Sie haben Recht, dass andere Muscheln in Betracht gezogen werden sollten, und Neulinge, die lesen, um aus Antworten zu lernen, sollten ebenfalls in Betracht gezogen werden. Ein kurzer Kommentar würde wahrscheinlich ausreichen, um den Punkt zu verdeutlichen.
Sergiy Kolodyazhnyy