Pourquoi $ '\ 0' est-il identique à ''?

10

Une façon courante de faire les choses avec quelques fichiers est — et ne me frappez pas pour cela:

for f in $(ls); do 

Maintenant, pour être à l'abri des fichiers contenant des espaces ou d'autres caractères étranges, une manière naïve serait de faire:

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

Ici, le -d ''est court pour définir le NUL ASCII comme dans -d $'\0'.

Mais pourquoi en est-il ainsi? Pourquoi ''et $'\0'la même chose? Est-ce dû aux racines C de Bash avec une chaîne vide toujours terminée par un caractère nul?

slhck
la source
En ce qui concerne la manière "naïve", existe-t-il une meilleure façon de procéder?
iruvar
2
Soit dit en passant, si vous souhaitez effectuer des opérations sûres en itérant sur un ensemble de fichiers - utilisez for f in *plutôt que d'analyser ls.
@htor Je sais que for i in $(ls)c'est terriblement stupide - j'ai presque honte de l'avoir utilisé comme mauvais exemple ici.
slhck
@ChandraRavoori Oui, par exemple en utilisant find … -execau lieu de boucler autour des fichiers, ce qui fonctionne dans la plupart des cas où vous utiliseriez une telle boucle for à la place. Ici, finds'occupe de tout pour vous.
slhck
@slhck, merci. Qu'en est-il des situations impliquant des opérations en plusieurs étapes sur chaque fichier où une boucle peut être préférable pour des raisons de lisibilité? Y a-t-il une meilleure option de boucle que la "voie naïve" ci-dessus?
iruvar

Réponses:

10

Le man page of bashlit:

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

Étant donné que les chaînes sont généralement terminées par null, le premier caractère d'une chaîne vide est l'octet nul. - Ça a du sens pour moi. :)

La source lit:

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

Pour une chaîne vide delimest simplement l'octet nul.

michas
la source
Lorsque vous dites que "les chaînes sont généralement terminées par null", n'est-ce pas le cas quelque part dans un environnement POSIX? Depuis l'époque où j'apprenais le C pour l'école, il est bien sûr logique de le supposer; Je vérifiais juste.
slhck
Mais on pourrait considérer n'importe quelle chaîne comme contenant arbitrairement de nombreuses chaînes vides, par exemple si vous concaténez '' et "X" vous obtenez "X". Donc, vous pourriez dire que la première rencontre bash de sous-chaîne est la chaîne vide. Par exemple, si vous utilisez la chaîne vide dans javascript, split()elle se divisera entre chaque caractère. Je soupçonne qu'un «pour des raisons historiques» pourrait être la meilleure explication que nous puissions obtenir.
donothingsuccess
Eh bien, pas tout à fait parce que "concaténer" un style C '\0'avec 'X\0'devrait vous donner 'X\0', si c'est bien fait. Cela n'a pas grand-chose à voir avec les fonctions de haut niveau dans des langages tels que JavaScript @don
slhck
Merci, michas, d'avoir ajouté la source. delim = *list_optarg;explique pourquoi il en est ainsi.
slhck
@slhck: Désolé, je n'ai pas été clair. Vous avez demandé "pourquoi sont ''-ils $'\0'les mêmes?", Michas a donné l'explication immédiate de "c'est ce que fait le code". J'ai décrit une autre façon de gérer la chaîne vide que je considérais comme tout aussi raisonnable et j'ai suggéré que choisir l'un ou l'autre était simplement une question de convention ou de hasard.
donothingsuccess
6

Il y a deux lacunes dans bash qui se compensent.

Lorsque vous écrivez $'\0', cela est traité en interne de manière identique à la chaîne vide. Par exemple:

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

C'est parce que bash stocke en interne toutes les chaînes sous forme de chaînes C , qui se terminent par un caractère nul - un octet nul marque la fin de la chaîne. Bash tronque silencieusement la chaîne au premier octet nul (qui ne fait pas partie de la chaîne!).

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

Lorsque vous passez une chaîne comme argument à l' -doption de la commande readintégrée, bash ne regarde que le premier octet de la chaîne. Mais il ne vérifie pas réellement que la chaîne n'est pas vide. En interne, une chaîne vide est représentée comme un tableau d'octets à 1 élément qui contient juste un octet nul. Ainsi, au lieu de lire le premier octet de la chaîne, bash lit cet octet nul.

Ensuite, en interne, la machinerie derrière la readfonction intégrée fonctionne bien avec des octets nuls; il continue à lire octet par octet jusqu'à ce qu'il trouve le délimiteur.

D'autres obus se comportent différemment. Par exemple, ash et ksh ignorent les octets nuls lorsqu'ils lisent l'entrée. Avec ksh, ksh -d ""lit jusqu'à une nouvelle ligne. Les shells sont conçus pour bien gérer le texte, pas les données binaires. Zsh est une exception: il utilise une représentation sous forme de chaîne qui accepte des octets arbitraires, y compris des octets nuls; en zsh, $'\0'est une chaîne de longueur 1 (mais read -d '', bizarrement, se comporte comme read -d $'\0').

Gilles 'SO- arrête d'être méchant'
la source
Le comportement de readchangé dans bash 4.3 de sorte qu'il ignore maintenant les octets nuls. Par exemple, read x< <(printf a\\0a)définit sur xau aalieu de a.
Lri