web-dev-qa-db-fra.com

Attribuer une chaîne contenant un caractère nul (\ 0) à une variable dans Bash

En essayant de traiter correctement une liste de noms de fichiers/dossiers ( voir mes autres questions ) en utilisant un caractère NULL comme délimiteur, je suis tombé sur un comportement étrange de Bash que je ne comprends pas :

Lors de l'affectation d'une chaîne contenant un ou plusieurs caractères NULL à une variable, les caractères NULL sont perdus/ignorés/non stockés.

Par exemple,

echo -ne "n\0m\0k" | od -c   # -> 0000000   n  \0   m  \0   k

Mais:

VAR1=`echo -ne "n\0m\0k"`
echo -ne "$VAR1" | od -c   # -> 0000000   n   m   k

Cela signifie que j'aurais besoin d'écrire cette chaîne dans un fichier (par exemple, dans/tmp) et de la relire à partir de là si la tuyauterie directe n'est pas souhaitée ou faisable.

Lors de l'exécution de ces scripts dans Z Shell (zsh), les chaînes contenant\0 sont conservées dans les deux cas, mais malheureusement, je ne peux pas supposer que zsh est présent dans les systèmes exécutant mon script alors que Bash devrait l'être.

Comment les chaînes contenant\0 caractères peuvent-elles être stockées ou gérées efficacement sans perdre de (méta) caractères?

29
antiplex

Dans Bash, vous ne pouvez pas stocker le caractère NULL dans une variable.

Vous pouvez, cependant, stocker un vidage hexadécimal simple des données (et inverser ultérieurement cette opération à nouveau) à l'aide de la commande xxd.

VAR1=`echo -ne "n\0m\0k" | xxd -p | tr -d '\n'`
echo -ne "$VAR1" | xxd -r -p | od -c   # -> 0000000    n  \0   m  \0   k
31
jeff

Comme d'autres l'ont déjà dit, vous ne pouvez pas stocker/utiliser le caractère NUL :

  • dans une variable
  • dans un argument de la ligne de commande.

Cependant, vous pouvez gérer toutes les données binaires (y compris le caractère NUL):

  • dans les tuyaux
  • dans les fichiers

Alors pour répondre à votre dernière question:

quelqu'un peut-il me donner un indice sur la manière dont les chaînes contenant des caractères\0 peuvent être stockées ou traitées efficacement sans perdre de (méta) caractères?

Vous pouvez utiliser des fichiers ou des tubes pour stocker et gérer efficacement n'importe quelle chaîne avec des méta-caractères.

Si vous envisagez de traiter des données, vous devez également noter que:

Contournement des limitations

Si vous voulez utiliser des variables, vous devez vous débarrasser du caractère NUL en l'encodant, et diverses autres solutions ici donnent des moyens intelligents de le faire (une manière évidente est d'utiliser par exemple l'encodage/décodage base64).

Si vous êtes préoccupé par la mémoire ou la vitesse, vous voudrez probablement utiliser un analyseur minimal et ne citer que le caractère NUL (et le caractère entre guillemets). Dans ce cas, cela vous aiderait:

quote() { sed 's/\\/\\\\/g;s/\x0/\\x00/g'; }

Ensuite, vous pouvez sécuriser vos données avant de les stocker dans des variables et un argument de ligne de commande en redirigeant vos données sensibles vers quote, qui produira un flux de données sécurisé sans caractères NUL. Vous pouvez récupérer la chaîne d'origine (avec les caractères NUL) en utilisant echo -en "$var_quoted" Qui enverra la chaîne correcte sur la sortie standard.

Exemple:

## Our example output generator, with NUL chars
ascii_table() { echo -en "$(echo '\'0{0..3}{0..7}{0..7} | tr -d " ")"; }
## store
myvar_quoted=$(ascii_table | quote)
## use
echo -en "$myvar_quoted"

Remarque: utilisez | hd Pour obtenir une vue claire de vos données en hexadécimal et vérifiez que vous n'avez perdu aucun caractère NUL.

Changement d'outils

N'oubliez pas que vous pouvez aller assez loin avec les tubes sans utiliser de variables ni d'argument en ligne de commande, n'oubliez pas par exemple la construction <(command ...) qui créera un tube nommé (sorte de fichier temporaire).

EDIT: la première implémentation de quote était incorrecte et ne traiterait pas correctement les caractères spéciaux \ Interprétés par echo -en. Merci @xhienne pour l'avoir repéré.

EDIT2: la deuxième implémentation de quote avait un bogue en raison de l'utilisation de seulement \0 Qui mangerait plus de zéros comme \0, \00, \000 Et \0000 Sont équivalents. Donc \0 A été remplacé par \x00. Merci à @MatthijsSteen d'avoir repéré celui-ci.

18
vaab

Utilisez uuencode et uudecode pour la portabilité POSIX

xxd et base64ne sont pas POSIX 7 mais encode est .

VAR="$(uuencode -m <(printf "a\0\n") /dev/stdout)"
uudecode -o /dev/stdout <(printf "$VAR") | od -tx1

Production:

0000000 61 00 0a
0000003

Malheureusement, je ne vois pas d'alternative POSIX 7 pour l'extension de substitution du processus Bash <() sauf l'écriture dans un fichier, et elles ne sont pas installées dans Ubuntu 12.04 par défaut (package sharutils).

Donc, je suppose que la vraie réponse est: n'utilisez pas Bash pour cela, utilisez Python ou un autre langage interprété plus sain.

J'adore réponse de Jeff . J'utiliserais l'encodage Base64 au lieu de xxd. Cela économise un peu d'espace et serait (je pense) plus reconnaissable quant à ce qui est prévu.

VAR=$(echo -ne "foo\0bar" | base64)
echo -n "$VAR" | base64 -d | xargs -0 ...

Quant à -e, il est nécessaire pour l'écho d'une chaîne littérale avec un null encodé ('\ 0'), bien que je semble également me rappeler quelque chose à propos de "echo -e" étant dangereux si vous faites écho à une entrée utilisateur comme ils pourraient injecter des séquences d'échappement que l'écho interprétera et aboutira à de mauvaises choses. L'indicateur -e n'est pas nécessaire lors de l'écho de la chaîne stockée encodée dans le décodage.

3
vontrapp