Por que $ '\ 0' é o mesmo que ''?

10

Uma maneira comum de fazer as coisas com alguns arquivos é - e não me bata por isso:

for f in $(ls); do 

Agora, para estar seguro contra arquivos com espaços ou outros caracteres estranhos, uma maneira ingênua seria:

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

Aqui, -d ''é a abreviação de definir o ASCII NUL como em -d $'\0'.

Mas porque é isso? Por que são ''e $'\0'os mesmos? Isso ocorre devido às raízes C do Bash, com uma string vazia sempre sendo terminada em nulo?

slhck
fonte
Referindo-se à maneira "ingênua", existe uma maneira melhor de fazer isso?
Iruvar
2
A propósito, se você deseja executar operações seguras repetindo um conjunto de arquivos - use em for f in *vez de analisar ls.
@ htor que eu sei que for i in $(ls)é terrivelmente estúpido - estou quase com vergonha de ter usado isso como um mau exemplo aqui.
slhck
@ChandraRavoori Sim, por exemplo, usando em find … -execvez de repetir arquivos, o que funciona na maioria dos casos em que você usaria um loop for. Aqui, findcuida de tudo para você.
slhck
@ slhck, obrigado. E as situações que envolvem operações com várias etapas em cada arquivo em que um loop pode ser preferível por motivos de legibilidade? Existe uma opção de loop melhor do que a "maneira ingênua" acima?
iruvar 12/01

Respostas:

10

As man page of bashleituras:

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

Como as strings geralmente são terminadas em nulo, o primeiro caractere de uma string vazia é o byte nulo. - Faz sentido para mim. :)

A fonte diz:

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

Para uma sequência vazia, delimé simplesmente o byte nulo.

michas
fonte
Quando você diz "seqüências de caracteres geralmente são terminadas em nulo", esse não é o caso em algum lugar de um ambiente POSIX? Desde os dias em que eu estava aprendendo C na escola, é claro que faz sentido supor isso; Eu estava apenas checando.
slhck
Mas pode-se considerar qualquer string contendo arbitrariamente muitas strings vazias, por exemplo, se você concatenar '' e "X", obtém "X". Portanto, você pode argumentar que o primeiro encontro de substring é a string vazia. Por exemplo, se você usar a string vazia em javascript, split()ela será dividida entre cada caractere. Suspeito que "por razões históricas" possa ser a melhor explicação que podemos obter.
precisa saber é o seguinte
Bem, não exatamente porque "concatenar" um estilo C '\0'com 'X\0'deve dar a você 'X\0', se bem feito. Isto não tem muito a ver com funções de alto nível em linguagens como JavaScript @don
slhck
Obrigado, michas, por adicionar a fonte. delim = *list_optarg;deixa claro por que é assim.
slhck 12/01
@slhck: Desculpe, não me deixei claro. Você perguntou "por que são ''e são $'\0'iguais?", Michas deu a explicação aproximada de "é isso que o código faz". Descrevi uma maneira alternativa de lidar com a cadeia vazia que considerava igualmente razoável e sugeri que escolher uma ou outra era simplesmente uma questão de convenção ou acontecimento.
precisa saber é o seguinte
6

Existem duas deficiências no bash que se compensam.

Quando você escreve $'\0', isso é tratado internamente de forma idêntica à sequência vazia. Por exemplo:

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

Isso ocorre porque internamente o bash armazena todas as seqüências de caracteres como C , com terminação nula - um byte nulo marca o final da sequência. O Bash silenciosamente trunca a string para o primeiro byte nulo (que não faz parte da string!).

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

Quando você passa uma string como argumento para a -dopção do readbuiltin, o bash analisa apenas o primeiro byte da string. Mas na verdade não verifica se a string não está vazia. Internamente, uma sequência vazia é representada como uma matriz de bytes de 1 elemento que contém apenas um byte nulo. Portanto, em vez de ler o primeiro byte da string, o bash lê esse byte nulo.

Em seguida, internamente, o mecanismo por trás do readbuilt-in funciona bem com bytes nulos; ele continua lendo byte a byte até encontrar o delimitador.

Outras conchas se comportam de maneira diferente. Por exemplo, ash e ksh ignoram bytes nulos quando lêem a entrada. Com ksh, ksh -d ""lê até uma nova linha. Os shells são projetados para lidar bem com texto, não com dados binários. Zsh é uma exceção: ele usa uma representação de string que lida com bytes arbitrários, incluindo bytes nulos; em zsh, $'\0'é uma sequência de comprimento 1 (mas read -d '', estranhamente, se comporta como read -d $'\0').

Gilles 'SO- parar de ser mau'
fonte
O comportamento de readmudou no bash 4.3 para que agora pule bytes nulos. Por exemplo, read x< <(printf a\\0a)define xpara em aavez de a.
Lri