Assegna una stringa contenente carattere null (\ 0) a una variabile in Bash

Durante il tentativo di elaborare un elenco di file / nomi personalizzati correttamente ( vedere le altre mie domande ) tramite l’uso di un carattere NULL come delimitatore, mi sono imbattuto in uno strano comportamento di Bash che non capisco:

Quando si assegna una stringa contenente uno o più caratteri NULL a una variabile, i caratteri NULL vengono persi / ignorati / non memorizzati.

Per esempio,

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

Ma:

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

Ciò significa che avrei bisogno di scrivere quella stringa su un file (ad esempio, in / tmp) e leggerla da lì se il piping non è desiderato o fattibile direttamente.

Quando esegui questi script nella shell Z (zsh) le stringhe contenenti \ 0 sono conservate in entrambi i casi, ma purtroppo non posso presumere che zsh sia presente nei sistemi che eseguono il mio script mentre Bash dovrebbe essere.

In che modo le stringhe contenenti i caratteri \ 0 possono essere memorizzate o gestite in modo efficiente senza perdere alcun (meta-) personaggio?

In Bash, non è ansible memorizzare il carattere NULL in una variabile.

Tuttavia, è ansible memorizzare un semplice dump esadecimale dei dati (e successivamente invertire di nuovo questa operazione) utilizzando il comando 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 

Come altri hanno già affermato, non è ansible memorizzare / utilizzare il carattere NUL :

  • in una variabile
  • in un argomento della riga di comando.

Tuttavia, puoi gestire qualsiasi dato binario (incluso il carattere NUL):

  • in tubi
  • nei file

Quindi per rispondere alla tua ultima domanda:

qualcuno può darmi un suggerimento su come le stringhe contenenti i caratteri \ 0 possono essere memorizzate o gestite in modo efficiente senza perdere alcun (meta-) personaggio?

È ansible utilizzare file o pipe per archiviare e gestire in modo efficiente qualsiasi stringa con eventuali meta-caratteri.

Se hai intenzione di gestire i dati, dovresti notare inoltre che:

  • Solo il carattere NUL verrà consumato dalla variabile e dall’argomento della riga di comando, puoi verificarlo .
  • Siate cauti che la sostituzione di comando (come $(command..) o `command..` ) ha un’ulteriore svolta sopra di essere una variabile in quanto mangerà le vostre nuove linee finali .

Limitare i limiti

Se vuoi usare le variabili, allora devi liberarti del NUL char codificandolo, e varie altre soluzioni qui forniscono modi intelligenti per farlo (un modo ovvio è usare, ad esempio, codifica / decodifica base64).

Se sei preoccupato per la memoria o la velocità, probabilmente vorrai usare un parser minimo e solo citare il carattere NUL (e il carattere di citazione). In questo caso questo ti aiuterà:

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

Quindi, è ansible proteggere i dati prima di memorizzarli nelle variabili e nell’argomento della riga di comando pipettando i dati sensibili in quote , che genereranno un stream di dati sicuro senza caratteri NUL. Puoi recuperare la stringa originale (con i caratteri NUL) usando echo -en "$var_quoted" che invierà la stringa corretta sullo standard output.

Esempio:

 ## 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" 

Nota: usare | hd | hd per ottenere una visualizzazione chiara dei dati in formato esadecimale e verificare di non aver perso alcun carattere NUL.

Cambiando strumenti

Ricorda che puoi andare molto lontano con le pipe senza usare variabili o argomenti nella riga di comando, non dimenticare per esempio il costrutto <(command ...) che creerà una named pipe (sorta di un file temporaneo).

EDIT: la prima implementazione dell'offerta non era corretta e non avrebbe funzionato correttamente con \ caratteri speciali interpretati da echo -en . Grazie a @xhienne per averlo scoperto.

Usa uuencode e uudecode per la portabilità POSIX

xxd e base64 non sono POSIX 7 ma uuencode lo è .

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

Produzione:

 0000000 61 00 0a 0000003 

Sfortunatamente non vedo un'alternativa POSIX 7 per l'estensione di sostituzione del processo Bash <() eccezione della scrittura su file, e non sono installati in Ubuntu 12.04 per impostazione predefinita (pacchetto sharutils ).

Quindi credo che la vera risposta sia: non usare Bash per questo, usa Python o qualche altro linguaggio interpretato da saner.

Amo la risposta di jeff . Userei la codifica Base64 invece di xxd. Risparmia un po ‘di spazio e sarebbe (penso) più riconoscibile per ciò che è destinato.

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

Per quanto riguarda -e, non è necessario perché la shell interpreta già la fuga prima ancora che arrivi all’eco. Mi sembra anche di ricordare che “echo -e” non è sicuro se si fa eco a qualsiasi input dell’utente poiché potrebbero iniettare sequenze di escape che l’eco interpreterà e finire con cose brutte.