Perché $ '\ 0' è uguale a ''?


10

Un modo comune per fare le cose con un paio di file è — e non colpirmi per questo:

for f in $(ls); do 

Ora, per essere al sicuro dai file con spazi o altri strani personaggi, un modo ingenuo sarebbe fare:

find . -type f -print0 | while IFS= read -r -d '' file; 

Qui -d ''è l'abbreviazione per impostare ASCII NUL come in -d $'\0'.

Ma perché è così? Perché sono ''e $'\0'lo stesso? Ciò è dovuto alle radici C di Bash con una stringa vuota che viene sempre terminata con null?


Riferendosi al modo "ingenuo", esiste un modo migliore per farlo?
Iruvar,

2
A proposito, se vuoi fare operazioni sicure iterando su un set di file, usa for f in *invece di analizzare ls.

@htor so che for i in $(ls)è terribilmente stupido — mi vergogno quasi di averlo usato come un cattivo esempio qui.
slhck,

@ChandraRavoori Sì, ad esempio utilizzando find … -execinvece di eseguire il ciclo attorno ai file, il che funziona nella maggior parte dei casi in cui si dovrebbe utilizzare tale ciclo for. Qui, si findprende cura di tutto per te.
slhck,

@slhck, grazie. Che dire delle situazioni che coinvolgono operazioni in più passaggi su ciascun file in cui un ciclo può essere preferibile per motivi di leggibilità? Esiste un'opzione loop migliore rispetto alla "via ingenua" sopra?
Iruvar,

Risposte:


10

Le man page of bashletture:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

Poiché le stringhe di solito hanno una terminazione nulla, il primo carattere di una stringa vuota è il byte null. - Per me ha senso. :)

La fonte dice:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

Per una stringa vuota delimè semplicemente il byte null.


Quando si dice "le stringhe sono normalmente terminate con null", non è forse il caso da qualche parte in un ambiente POSIX? Dai tempi in cui stavo imparando il C a scuola, ovviamente ha senso assumerlo; Stavo solo controllando.
Slhck,

Ma si potrebbe considerare qualsiasi stringa contenente arbitrariamente molte stringhe vuote, ad esempio se si concatenano '' e "X" si ottiene "X". Quindi si potrebbe sostenere che la prima sottostringa che incontra bash è la stringa vuota. Ad esempio, se si utilizza la stringa vuota in JavaScript, split()questa verrà suddivisa tra ciascun carattere. Sospetto che "per ragioni storiche" possa essere la migliore spiegazione che possiamo ottenere.
donothingsuccessivamente il

Beh, non proprio perché "concatenazione" uno stile di C '\0'con 'X\0'dovrebbe darvi 'X\0', se right Fatto. Questo non ha molto a che fare con le funzioni di alto livello in linguaggi come JavaScript @don
slhck

Grazie, michas, per aver aggiunto la fonte. delim = *list_optarg;chiarisce perché è così.
Slhck,

@slhck: mi dispiace, non mi sono chiarito. Hai chiesto "perché sei ''e $'\0'lo stesso?", I micha hanno dato la spiegazione immediata di "questo è ciò che fa il codice". Ho delineato un modo alternativo di gestire la stringa vuota che ho ritenuto ugualmente ragionevole e ho suggerito che scegliere l'una o l'altra era semplicemente una questione di convenzione o di casualità.
donothingsuccessivamente

6

Ci sono due carenze in bash che si compensano a vicenda.

Quando scrivi $'\0', questo viene trattato internamente in modo identico alla stringa vuota. Per esempio:

$ a=$'\0'; echo ${#a}
0

Questo perché internamente bash memorizza tutte le stringhe come stringhe C , che hanno una terminazione nulla - un byte null segna la fine della stringa. Bash tronca silenziosamente la stringa al primo byte null (che non fa parte della stringa!).

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

Quando si passa una stringa come argomento -dall'opzione del readbuiltin, bash guarda solo il primo byte della stringa. Ma in realtà non controlla che la stringa non sia vuota. Internamente, una stringa vuota è rappresentata come un array di byte a 1 elemento che contiene solo un byte null. Quindi invece di leggere il primo byte della stringa, bash legge questo byte null.

Quindi, internamente, il macchinario dietro l' readintegrato funziona bene con byte nulli; continua a leggere byte per byte fino a quando non trova il delimitatore.

Altre shell si comportano diversamente. Ad esempio, ash e ksh ignorano i byte null quando leggono l'input. Con ksh, ksh -d ""legge fino a una nuova riga. Le conchiglie sono progettate per far fronte bene al testo, non ai dati binari. Zsh è un'eccezione: utilizza una rappresentazione in formato stringa che gestisce byte arbitrari, inclusi i byte null; in zsh, $'\0'è una stringa di lunghezza 1 (ma read -d '', stranamente, si comporta come read -d $'\0').


Il comportamento di readcambiato in bash 4.3 in modo che ora salti byte nulli. Ad esempio read x< <(printf a\\0a)imposta xsu aaanziché a.
Lri,
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.