bash ne peut pas stocker la valeur hexadécimale 0x00 dans la variable

11

J'essaye de faire quelques tours avec dd. J'ai pensé qu'il serait possible de stocker des valeurs hexadécimales dans une variable appelée "en-tête" pour la diriger vers dd.

Ma première étape sans variable a été la suivante:

$ echo -ne "\x36\xc9\xda\x00\xb4" |dd of=hex
$ hd hex

00000000  36 c9 da 00 b4                                    |6....|
00000005

Après cela, j'ai essayé ceci:

$ header=$(echo -ne "\x36\xc9\xda\x00\xb4") 
$ echo -n $header | hd

00000000  36 c9 da b4                                       |6...|
00000004

Comme vous pouvez le voir, j'ai perdu ma \x00valeur dans la $headervariable. Quelqu'un at-il une explication à ce comportement? Ça me rend fou.

Franc
la source
Je comprends bash: warning: command substitution: ignored null byte in input.
Kusalananda
Il vous manque des guillemets, header="$(echo -ne "\x36\xc9\xda\x00\xb4")"; echo -n "$header" | hdmais cela donne juste le même résultat.
ctrl-alt-delor
Cela fonctionne header="\x36\xc9\xda\x00\xb4"; echo -n "$header" | hd, mais ce n'est pas la même chose que de stocker la forme lisible par l'homme.
ctrl-alt-delor

Réponses:

16

Vous ne pouvez pas stocker un octet nul dans une chaîne car Bash utilise des chaînes de style C, qui réservent l'octet nul aux terminateurs. Vous devez donc réécrire votre script pour simplement diriger la séquence qui contient l'octet nul sans que Bash ait besoin de le stocker au milieu. Par exemple, vous pouvez faire ceci:

printf "\x36\xc9\xda\x00\xb4" | hd

Soit dit en passant, vous n'avez pas besoin echo; vous pouvez utiliser Bash printfpour de nombreuses autres tâches simples.

Ou au lieu de chaîner, vous pouvez utiliser un fichier temporaire:

printf "\x36\xc9\xda\x00\xb4" > /tmp/mysequence
hd /tmp/mysequence

Bien sûr, cela a le problème que le fichier /tmp/mysequenceexiste peut-être déjà. Et maintenant, vous devez continuer à créer des fichiers temporaires et à enregistrer leurs chemins dans des chaînes.

Ou vous pouvez éviter cela en utilisant la substitution de processus:

hd <(printf "\x36\xc9\xda\x00\xb4")

L' <(command)opérateur crée un canal nommé dans le système de fichiers, qui recevra la sortie de command. hdrecevra, comme premier argument, le chemin d'accès à ce canal, qu'il ouvrira et lira presque comme n'importe quel fichier. Vous pouvez en savoir plus à ce sujet ici: /unix//a/17117/136742 .

giusti
la source
1
Bien que correct, il s'agit d'un détail de mise en œuvre et non de la raison exacte. Je l'ai regardé, et la norme POSIX nécessite en fait ce comportement, donc vous avez la vraie raison. (Comme certains l'ont souligné, le zshfera, mais uniquement en mode nōn-POSIX.) Je l'ai en fait examiné parce que je me demandais si cela valait la peine de l'implémenter dans mksh
mirabilos
@mirabilos, voudriez-vous développer cela? AFAICT, le comportement n'est pas spécifié par POSIX pour la substitution de commandes lorsque la sortie a des caractères NUL, et pour zsh en mode POSIX, la seule différence pertinente à laquelle je peux penser est que dans l' shémulation, \0n'est pas dans la valeur par défaut de $IFS. echo "$(printf 'a\0b')"fonctionne toujours bien dans l' shémulation zsh.
Stéphane Chazelas
3
@mirabilos Étant donné que les shells sont antérieurs à la norme POSIX d'une décennie ou plus, je suppose que vous pourriez découvrir que la véritable raison est que les shells utilisaient des chaînes de style C et que la norme a été construite autour de cela.
giusti
J'ai trouvé un bon Q pour une discussion détaillée sur printf versus echo. unix.stackexchange.com/questions/65803/…
Paulb
8

Vous pouvez utiliser à la zshplace qui est le seul shell qui peut stocker le caractère NUL dans ses variables. Ce caractère se trouve même être dans la valeur par défaut de $IFSin zsh.

nul=$'\0'

Ou:

nul=$'\x0'

Ou

nul=$'\u0000'

Ou

nul=$(printf '\0')

Cependant, notez que vous ne pouvez pas transmettre une telle variable en tant qu'argument ou variable d'environnement à une commande qui est exécutée car les arguments et les variables d'environnement sont des chaînes délimitées par NUL transmises à l' execve()appel système (une limitation de l'API du système, pas du shell ). Dans zsh, vous pouvez cependant passer des octets NUL comme arguments aux fonctions ou commandes intégrées.

echo $'\0' # works
/bin/echo $'\0' # doesn't
Stéphane Chazelas
la source
1
"Vous pouvez utiliser zsh à la place". Non merci - j'apprends moi-même le bash-scripting en tant que débutant. Je ne veux pas me confondre avec une autre syntaxe. Mais merci beaucoup de l'avoir suggéré
Frank
En fait, vous avez utilisé la zshsyntaxe dans votre question. echo -n $headersignifier passer le contenu de la $headervariable comme dernier argument à echo -nest zsh(ou fishou rcou es) la syntaxe, pas la bash syntaxe. Dans bash, cela a une signification très différente . Plus généralement, zshc'est comme ksh( bashle shell GNU, étant plus ou moins un clone partiel kshdu shell Unix de facto) mais avec la plupart des particularités de conception du shell Bourne corrigées (et beaucoup de fonctionnalités supplémentaires, et beaucoup plus convivial / moins étonnant).
Stéphane Chazelas
Attention: zsh peut parfois changer un zéro octet: echo $(printf 'ab\0cd') | od -vAn -tx1c imprime `61 62 20 63 64 0a`, c'est-à-dire un espace où un NUL devrait exister.
Isaac
1
Et c'est quelque chose qu'aucun autre shell (aucun, aucun) ne reproduira. Cela fait qu'un script se comporte de manière très spéciale dans zsh. À mon avis: zsh essaie juste d'être trop intelligent.
Isaac
2
Ayant "corrigé" les défauts de conception présents dans la norme POSIX sh qui s'habituent à écrire des scripts zsh signifie que l'on s'habitue à des pratiques qui seraient boguées si elles étaient exercées dans un autre shell. Ce n'est pas un problème avec une syntaxe si différente d'une langue différente que les compétences ou les habitudes ne sont pas susceptibles de se transférer, mais ce n'est pas le cas.
Charles Duffy