Warum ist $ '\ 0' dasselbe wie ''?

10

Ein üblicher Weg, Dinge mit ein paar Dateien zu tun, ist - und schlagen Sie mich nicht dafür:

for f in $(ls); do 

Um vor Dateien mit Leerzeichen oder anderen seltsamen Zeichen sicher zu sein, wäre ein naiver Weg:

find . -type f -print0 | while IFS= read -r -d '' file; 

Hier -d ''steht kurz für das Einstellen des ASCII NUL wie in -d $'\0'.

Aber warum ist das so? Warum sind ''und $'\0'das gleiche? Liegt das daran, dass die C-Wurzeln von Bash mit einer leeren Zeichenfolge immer nullterminiert sind?

slhck
quelle
Gibt es in Bezug auf den "naiven" Weg einen besseren Weg, dies zu tun?
iruvar
2
Übrigens, wenn Sie sichere Operationen ausführen möchten, die über eine Reihe von Dateien iterieren, verwenden Sie diese Option, for f in *anstatt sie zu analysieren ls.
@htor Ich weiß, for i in $(ls)ist schrecklich dumm - ich schäme mich fast, dass ich es hier als schlechtes Beispiel verwendet habe.
Slhck
@ChandraRavoori Ja, zum Beispiel, indem Sie find … -execDateien anstelle von Schleifen verwenden, was in den meisten Fällen funktioniert, in denen Sie stattdessen eine solche for-Schleife verwenden würden. Hier findkümmert sich alles um Sie.
Slhck
@slhck, danke. Was ist mit Situationen mit mehrstufigen Operationen für jede Datei, in denen eine Schleife aus Gründen der Lesbarkeit vorzuziehen ist? Gibt es eine bessere Loop-Option als den oben genannten "naiven Weg"?
iruvar

Antworten:

10

Das man page of bashliest:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

Da Zeichenfolgen normalerweise mit Null abgeschlossen sind, ist das erste Zeichen einer leeren Zeichenfolge das Nullbyte. - Für mich ergibt das Sinn. :) :)

Die Quelle lautet:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

Für eine leere Zeichenfolge delimist einfach das Null-Byte.

michas
quelle
Wenn Sie sagen, dass Zeichenfolgen normalerweise nullterminiert sind, ist dies nicht irgendwo in einer POSIX-Umgebung der Fall? Von den Tagen an, als ich C für die Schule lernte, ist es natürlich sinnvoll, dies anzunehmen; Ich habe nur nachgesehen.
Slhck
Man könnte aber jede Zeichenfolge als beliebig viele leere Zeichenfolgen enthaltend betrachten, z. B. wenn Sie '' und "X" verketten, erhalten Sie "X". Man könnte also argumentieren, dass die erste Begegnung mit Teilzeichenfolgen die leere Zeichenfolge ist. Wenn Sie beispielsweise die leere Zeichenfolge in Javascript verwenden split(), wird sie zwischen den einzelnen Zeichen aufgeteilt. Ich vermute, dass ein "aus historischen Gründen" die beste Erklärung sein kann, die wir bekommen können.
donothings erfolgreich
Nun, nicht ganz, weil das "Verketten" eines C-Stils '\0'mit 'X\0'Ihnen geben sollte 'X\0', wenn es richtig gemacht wird. Dies hat nicht viel mit Funktionen auf hoher Ebene in Sprachen wie JavaScript @don zu tun
slhck
Danke, Michas, dass du die Quelle hinzugefügt hast. delim = *list_optarg;macht deutlich, warum es so ist.
Slhck
@slhck: Entschuldigung, ich habe mich nicht klar ausgedrückt. Sie fragten "Warum sind ''und $'\0'das gleiche?", Michas gab die unmittelbare Erklärung von "das ist, was der Code tut". Ich skizzierte eine alternative Art des Umgangs mit der leeren Zeichenfolge, die ich als ebenso vernünftig ansah, und schlug vor, dass die Wahl der einen oder anderen nur eine Frage der Konvention oder des Zufalls sei.
donothings erfolgreich
6

Es gibt zwei Mängel bei Bash, die sich gegenseitig ausgleichen.

Wenn Sie schreiben $'\0', wird dies intern identisch mit der leeren Zeichenfolge behandelt. Beispielsweise:

$ a=$'\0'; echo ${#a}
0

Dies liegt daran, dass intern bash alle Zeichenfolgen als C- Zeichenfolgen speichert , die nullterminiert sind. Ein Null- Byte markiert das Ende der Zeichenfolge. Bash schneidet die Zeichenfolge stillschweigend auf das erste Null-Byte ab (das nicht Teil der Zeichenfolge ist!).

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

Wenn Sie einen String als Argument an die -dOption des readeingebauten Strings übergeben, betrachtet bash nur das erste Byte des Strings. Es wird jedoch nicht überprüft, ob die Zeichenfolge nicht leer ist. Intern wird eine leere Zeichenfolge als 1-Element-Byte-Array dargestellt, das nur ein Null-Byte enthält. Anstatt das erste Byte der Zeichenfolge zu lesen, liest bash dieses Null-Byte.

readIntern funktioniert die Maschinerie hinter dem eingebauten System dann gut mit Null-Bytes. Es liest Byte für Byte weiter, bis es das Trennzeichen findet.

Andere Muscheln verhalten sich anders. Zum Beispiel ignorieren ash und ksh Nullbytes, wenn sie Eingaben lesen. ksh -d ""Liest mit ksh bis zu einem Zeilenumbruch. Shells sind so konzipiert, dass sie gut mit Text umgehen können, nicht mit Binärdaten. Zsh ist eine Ausnahme: Es wird eine Zeichenfolgendarstellung verwendet, die mit beliebigen Bytes, einschließlich Nullbytes, fertig wird. in zsh $'\0'ist eine Zeichenfolge der Länge 1 ( read -d ''verhält sich aber seltsamerweise wie read -d $'\0').

Gilles 'SO - hör auf böse zu sein'
quelle
Das Verhalten von wurde readin Bash 4.3 geändert, sodass jetzt Null-Bytes übersprungen werden. Zum Beispiel read x< <(printf a\\0a)setzt xauf aastatt a.
Lri