Como eu uso bytes nulos no Bash?

31

Eu li isso, já que os caminhos de arquivo no Bash podem conter qualquer caractere, exceto o byte nulo (byte de valor zero, $'find' ), que é melhor usar o byte nulo como um separador. Por exemplo, se a saída de -print0 for enviada para outro programa, é recomendável usar a opção find (para versões de for que a possuem).

Mas, embora algo assim funcione bem (impressão de caminhos de arquivo separados por novas linhas - não se preocupe, isso é apenas uma demonstração, não estou fazendo isso em scripts reais):

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

algo assim não funciona:

%pre%

Quando tento apenas a parte %code% -loop, descubro que apenas imprime todos os nomes dos arquivos juntos, sem o byte nulo entre eles.

Por que isso? O que está acontecendo?

    
por ruakh 13.12.2014 / 01:30

1 resposta

41

O Bash usa sequências de estilo C internamente, que são terminadas por bytes nulos. Isso significa que uma string de Bash (como o valor de uma variável ou um argumento para um comando) nunca pode realmente conter um byte nulo. Por exemplo, este mini-script:

foobar=$'foo
for file in * ; do printf '%s
printf '%s
foobar=$'foo
for file in * ; do printf '%s
printf '%s%pre%' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done
' "$file" ; done \ | while IFS= read -r -d '' ; do echo "$REPLY" ; done
bar' # foobar='foo' + null byte + 'bar' echo "${#foobar}" # print length of $foobar
' * \ | while IFS= read -r -d '' ; do echo "$REPLY" ; done
' "$file" ; done \ | while IFS= read -r -d '' ; do echo "$REPLY" ; done
bar' # foobar='foo' + null byte + 'bar' echo "${#foobar}" # print length of $foobar

na verdade imprime 3 , porque $foobar é na verdade apenas 'foo' : o bar vem depois do final da string.

Da mesma forma, echo $'foofoobar' apenas imprime echo , porque $'...'bar não sabe sobre a parte read .

Como você pode ver, a seqüência -d $'-d ''' é realmente muito enganadora em uma string read -style; parece um byte nulo dentro da string, mas não funciona dessa maneira. No seu primeiro exemplo, o comando '' tem -d delim . Isso funciona, mas só porque find também funciona! (Esse não é um recurso explicitamente documentado de $'echo "$file"$'echo'' , mas suponho que funcione pelo mesmo motivo: printf é a string vazia, então seu byte de terminação nulo vem imediatamente. $'...' é documentado como usando "O primeiro caractere de delimitar ", e acho que funciona mesmo se o" primeiro caractere "tiver passado do final da string!

Mas como você sabe do seu exemplo echo , é possível que um comando imprima um byte nulo, e que o byte seja canalizado para outro comando que o lê como entrada. Nenhuma parte disso depende do armazenamento de um byte nulo em uma string dentro do Bash . O único problema com seu segundo exemplo é que não podemos usar -e em um argumento para um comando; printf poderia felizmente imprimir o byte nulo no final, se soubesse que você queria.

Portanto, em vez de usar echo , você pode usar command echo , que suporta os mesmos tipos de seqüências de escape que as sequências com echo . Dessa forma, você pode imprimir um byte nulo sem ter que ter um byte nulo dentro de uma string. Isso ficaria assim:

%pre%

ou simplesmente isso:

%pre%

(Nota: echo na verdade também tem um $PATH sinalizador que permitiria processar %code% e imprimir um byte nulo; mas também tentaria processar qualquer sequência especial em seu nome de arquivo. Portanto, o %code% abordagem é mais robusta.)

Aliás, existem alguns shells que fazem permitem bytes nulos dentro de strings. Seu exemplo funciona bem no Zsh, por exemplo (assumindo configurações padrão). No entanto, independentemente do seu shell, sistemas operacionais Unix-like não fornecem uma maneira de incluir bytes nulos dentro de argumentos para programas (já que os argumentos do programa são passados como strings no estilo C), então sempre haverá algumas limitações. (Seu exemplo pode funcionar no Zsh somente porque %code% é um shell embutido, então o Zsh pode invocá-lo sem depender do suporte do sistema operacional para chamar outros programas. Se você usou %code% em vez de %code% , ele ignorou o embutido e usado o programa %code% autônomo no %code% , você veria o mesmo comportamento no Zsh como no Bash.

    
por 13.12.2014 / 01:30

Tags