Wie verwende ich Null-Bytes in Bash?

33

Ich habe gelesen, dass, da Dateipfade in Bash jedes Zeichen außer dem Null-Byte (nullwertiges Byte $'\0') enthalten können, es am besten ist, das Null-Byte als Trennzeichen zu verwenden. Wenn beispielsweise die Ausgabe von findan ein anderes Programm gesendet wird, wird empfohlen, die -print0Option zu verwenden (für Versionen mit finddieser Option ).

Aber obwohl so etwas gut funktioniert (Drucken von durch Zeilenumbrüche getrennten Dateipfaden - keine Sorge, dies ist nur eine Demonstration, ich mache es eigentlich nicht in echten Skripten):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

so etwas funktioniert nicht :

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

Wenn ich nur den for-loop-Teil versuche, stelle ich fest, dass nur alle Dateinamen zusammen gedruckt werden, ohne das Null-Byte dazwischen.

Warum ist das? Was ist los?

ruakh
quelle

Antworten:

43

Bash verwendet intern Strings im C-Stil, die mit Null-Bytes abgeschlossen werden. Dies bedeutet, dass eine Bash-Zeichenfolge (z. B. der Wert einer Variablen oder ein Argument für einen Befehl) niemals tatsächlich ein Null-Byte enthalten kann. Zum Beispiel dieses Mini-Skript:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

druckt eigentlich 3, weil $foobares eigentlich nur so ist 'foo': das barkommt nach dem Ende der Zeichenkette.

Ebenso wird echo $'foo\0bar'nur gedruckt foo, da echoüber das \0barTeil nichts bekannt ist .

Wie Sie sehen können, ist die \0Sequenz in einem $'...'String im -Stil tatsächlich sehr irreführend . Es sieht aus wie ein Null-Byte in der Zeichenfolge, aber es funktioniert nicht so. In Ihrem ersten Beispiel hat Ihr readBefehl -d $'\0'. Das funktioniert, aber nur weil es -d ''auch funktioniert! (Dies ist kein explizit dokumentiertes Feature von read, aber ich nehme an, es funktioniert aus dem gleichen Grund: Ist'' der String leer, so kommt das abschließende Null-Byte sofort. Es wird dokumentiert, dass "Das erste Zeichen von delim " verwendet wird, und ich vermute, dass dies sogar funktioniert wenn das "erste Zeichen" hinter dem Ende der Zeichenkette liegt!)-d delim

Aber wie Sie von Ihrem wissen findBeispiel, es ist möglich , dass ein Befehl einen Null - Byte auszudrucken, und für das Byte an einem anderen Befehl geleitet werden , dass sie als Eingabe liest. Kein Teil davon ist darauf angewiesen, ein Null-Byte in einer Zeichenfolge in Bash zu speichern . Das einzige Problem mit Ihrem zweiten Beispiel ist, dass wir kein $'\0'Argument für einen Befehl verwenden können. echo "$file"$'\0'Am Ende könnte man gerne das Null-Byte ausgeben, wenn man nur wüsste, dass man es haben möchte.

Anstatt also zu verwenden echo, können Sie verwenden printf, was dieselben Arten von Escape-Sequenzen unterstützt wie $'...'-Style-Strings. Auf diese Weise können Sie ein Null-Byte drucken, ohne dass ein Null-Byte in einer Zeichenfolge enthalten sein muss. Das würde so aussehen:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

oder einfach so:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(Hinweis: Hat echotatsächlich auch ein -eFlag, mit dem \0ein Null-Byte verarbeitet und gedruckt werden kann. Dann wird jedoch auch versucht, spezielle Sequenzen in Ihrem Dateinamen zu verarbeiten. Der printfAnsatz ist also robuster.)


Im Übrigen gibt es einige Muscheln , die keine Null - Bytes innerhalb von Zeichenketten ermöglichen. Ihr Beispiel funktioniert beispielsweise in Zsh einwandfrei (unter der Annahme von Standardeinstellungen). Unabhängig von Ihrer Shell bieten Unix-ähnliche Betriebssysteme jedoch keine Möglichkeit, Nullbytes in Argumente für Programme einzufügen (da Programmargumente als Zeichenfolgen im C-Stil übergeben werden), sodass es immer einige Einschränkungen gibt. (Ihr Beispiel kann nur in Zsh arbeiten , weil echoein Shell - builtin ist, so Zsh es , ohne sich auf die OS - Unterstützung zum Aufrufen anderer Programme aufrufen kann. Wenn Sie verwendet command echostatt echo, so dass es die eingebaute umgangen und die Standalone verwendet echoProgramm auf das $PATH, Sie würden das gleiche Verhalten in Zsh wie in Bash sehen.)

ruakh
quelle
2
Warum wird IFS auf nichts gesetzt, wenn es -d ''bereits bedeutet, abzugrenzen \0? Ich fand eine Erklärung hier: stackoverflow.com/questions/8677546/…
CMCDragonkai