Come si usano i byte null in Bash?


33

Ho letto che, poiché i percorsi dei file in Bash possono contenere qualsiasi carattere tranne il byte null (byte a valore zero, $'\0'), è meglio usare il byte null come separatore. Ad esempio, se l'output di findverrà inviato a un altro programma, si consiglia di utilizzare l' -print0opzione (per le versioni findche lo hanno).

Ma anche se qualcosa del genere funziona bene (stampando percorsi di file separati da newline - non ti preoccupare, questa è solo una dimostrazione, in realtà non lo sto facendo in veri e propri script):

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

qualcosa del genere non funziona:

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

Quando provo solo la forparte -loop, trovo che stampa tutti i nomi dei file insieme, senza il byte null in mezzo.

Perchè è questo? Cosa sta succedendo?

Risposte:


43

Bash utilizza stringhe di tipo C internamente, che sono terminate da byte null. Ciò significa che una stringa di Bash (come il valore di una variabile o un argomento di un comando) non può mai contenere un byte null. Ad esempio, questo mini-script:

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

in realtà stampa 3, perché in $foobarrealtà è solo 'foo': bararriva dopo la fine della stringa.

Allo stesso modo, echo $'foo\0bar'stampa solo fooperché echonon conosce la \0barparte.

Come puoi vedere, la \0sequenza è in realtà molto fuorviante in una $'...'stringa di tipo; sembra un byte nullo all'interno della stringa, ma non finisce per funzionare in questo modo. Nel tuo primo esempio, il tuo readcomando ha -d $'\0'. Funziona, ma solo perché -d ''funziona anche! (Questa non è una caratteristica esplicitamente documentata di read, ma suppongo che funzioni per lo stesso motivo: ''è la stringa vuota, quindi il suo byte null di terminazione arriva immediatamente. È documentato come usando "Il primo carattere del delim ", e immagino che funzioni anche se il "primo carattere" è oltre la fine della stringa!)-d delim

Ma come sai dal tuo findesempio, è possibile che un comando stampi un byte nullo e che quel byte sia reindirizzato a un altro comando che lo legge come input. Nessuna parte di ciò si basa sulla memorizzazione di un byte null in una stringa all'interno di Bash . L'unico problema con il tuo secondo esempio è che non possiamo usare $'\0'in un argomento un comando; echo "$file"$'\0'potrebbe felicemente stampare il byte null alla fine, se solo sapesse che lo volevi.

Quindi, invece di utilizzare echo, è possibile utilizzare printf, che supporta gli stessi tipi di sequenze di escape delle $'...'stringhe di stile. In questo modo, puoi stampare un byte null senza dover avere un byte null all'interno di una stringa. Sarebbe così:

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

o semplicemente questo:

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

(Nota: in echorealtà ha anche un -eflag che gli permetterebbe di elaborare \0e stampare un byte null; ma poi proverebbe anche a elaborare eventuali sequenze speciali nel tuo nome file. Quindi l' printfapproccio è più robusto.)


Per inciso, ci sono alcune conchiglie che non consentono nulla byte stringhe all'interno. Il tuo esempio funziona bene in Zsh, ad esempio (assumendo le impostazioni predefinite). Tuttavia, indipendentemente dalla shell, i sistemi operativi simili a Unix non forniscono un modo per includere byte nulli all'interno degli argomenti dei programmi (poiché gli argomenti del programma vengono passati come stringhe in stile C), quindi ci saranno sempre delle limitazioni. (Il tuo esempio può funzionare in Zsh solo perché echoè un built-in della shell, quindi Zsh può invocarlo senza fare affidamento sul supporto del sistema operativo per invocare altri programmi. Se lo hai utilizzato command echoinvece di echo, in modo da bypassare il builtin e utilizzare il echoprogramma autonomo sul $PATH, vedresti lo stesso comportamento in Zsh che in Bash.)


2
Perché IFS non è impostato su nulla se -d ''già intende delimitare \0? Ho trovato una spiegazione qui: stackoverflow.com/questions/8677546/...
CMCDragonkai
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.