Comment utiliser des octets nuls dans Bash?

33

J'ai lu que, puisque les chemins de fichiers dans Bash peuvent contenir n'importe quel caractère, à l'exception de l'octet nul (octet de valeur zéro $'\0'), il est préférable d'utiliser l'octet nul en tant que séparateur. Par exemple, si le résultat de findsera envoyé à un autre programme, il est recommandé d'utiliser l' -print0option (pour les versions de findcelui-ci).

Mais bien que quelque chose comme ça marche (impression des chemins de fichiers séparés par des nouvelles lignes - ne vous inquiétez pas, ceci est simplement une démonstration, je ne le fais pas dans de vrais scripts):

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

quelque chose comme ça ne marche pas :

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

Lorsque j'essaie uniquement la forpartie -loop, je constate qu'elle imprime simplement tous les noms de fichiers ensemble, sans l'octet nul entre les deux.

Pourquoi est-ce? Que se passe-t-il?

ruakh
la source

Réponses:

43

Bash utilise des chaînes de style C en interne, qui se terminent par des octets nuls. Cela signifie qu'une chaîne Bash (telle que la valeur d'une variable ou un argument d'une commande) ne peut jamais contenir un octet nul. Par exemple, ce mini-script:

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

imprime réellement 3, car $foobarest en réalité juste 'foo': le barvient après la fin de la chaîne.

De même, echo $'foo\0bar'juste des impressions foo, car echone sait pas à propos de la \0barpièce.

Comme vous pouvez le constater, la \0séquence est en réalité très trompeuse dans une $'...'chaîne de style; cela ressemble à un octet nul à l'intérieur de la chaîne, mais cela ne fonctionne pas de cette façon. Dans votre premier exemple, votre readcommande a -d $'\0'. Cela fonctionne, mais seulement parce que -d ''fonctionne aussi! (Ce n’est pas une fonctionnalité explicitement documentée de read, mais je suppose que cela fonctionne pour la même raison: ''c’est la chaîne vide, donc son octet nul final vient immédiatement. Est documenté comme utilisant "Le premier caractère de délimitation ", et je suppose que cela fonctionne même si le "premier caractère" est passé la fin de la chaîne!)-d delim

Mais comme vous le savez dans votre findexemple, il est possible qu'une commande imprime un octet nul et que cet octet soit acheminé vers une autre commande qui le lit en entrée. Aucune partie de cela ne repose sur le stockage d'un octet nul dans une chaîne à l'intérieur de Bash . Le seul problème avec votre deuxième exemple est que nous ne pouvons pas utiliser $'\0'dans un argument à une commande; echo "$file"$'\0'pourrait heureusement imprimer l'octet nul à la fin, si seulement il savait que vous le vouliez.

Donc, au lieu d'utiliser echo, vous pouvez utiliser printf, qui supporte les mêmes types de séquences d'échappement que les $'...'chaînes de style. De cette façon, vous pouvez imprimer un octet nul sans avoir à en avoir un dans une chaîne. Cela ressemblerait à ceci:

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

ou simplement ceci:

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

(Remarque: echodispose en fait d'un -eindicateur lui permettant de traiter \0et d'imprimer un octet nul; il essaiera ensuite de traiter les séquences spéciales de votre nom de fichier. L' printfapproche est donc plus robuste.)


Soit dit en passant, il y a des obus qui ne permettent des octets nuls chaînes à l' intérieur. Votre exemple fonctionne bien en Zsh, par exemple (en supposant les paramètres par défaut). Cependant, quel que soit votre shell, les systèmes d'exploitation de type Unix ne permettent pas d'inclure des octets nuls dans les arguments des programmes (car les arguments des programmes sont passés sous forme de chaînes de style C), il y aura donc toujours certaines limitations. (Votre exemple peut travailler dans zsh seulement parce echoest une commande du shell, donc zsh peut l' invoquer sans compter sur le Si vous avez utilisé le soutien du système d'exploitation pour appeler d' autres programmes. Au command echolieu de echo, de sorte qu'il contournée la commande interne et utilisé la version autonome de echoprogramme sur le $PATH, vous verriez le même comportement dans Zsh que dans Bash.)

ruakh
la source
2
Pourquoi IFS est-il réglé sur rien si cela -d ''signifie déjà de délimiter \0? J'ai trouvé une explication ici: stackoverflow.com/questions/8677546/…
CMCDragonkai