
Porque é isso que alguns deles estão fazendo.
> echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le > /tmp/hallo
> chmod 755 /tmp/hallo
> dash /tmp/hallo
Hallo, Baby!
> bash /tmp/hallo
/tmp/hallo: /tmp/hallo: cannot execute binary file
> (echo '#'; echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le) > /tmp/hallo
> bash /tmp/hallo
Hallo, Baby!
> mksh /tmp/hallo
Hallo, Baby!
> cat -v /tmp/hallo
#
e^@c^@h^@o^@ ^@H^@a^@l^@l^@o^@,^@ ^@B^@a^@b^@y^@!^@
^@
Isso é algum incômodo de compatibilidade, na verdadeobrigatóriopelo padrão? Porque parece bastante perigoso e inesperado.
Responder1
ConformePOSIX,
o arquivo de entrada deve ser um arquivo de texto, exceto que os comprimentos das linhas devem ser ilimitados¹
Caracteres NUL² na entradatorná-lo não-texto, então o comportamento não é especificado no que diz respeito ao POSIX, então sh
as implementações podem fazer o que quiserem (e um compatível com POSIXroteironão deve conter NULs).
Existem alguns shells que verificam os primeiros bytes em busca de 0s e se recusam a executar o script, supondo que você tentou executar um arquivo não-script por engano.
Isso é útil porque as exec*p()
funções, env
comandos, sh
, find -exec
... sãoobrigatóriochamar um shell para interpretar um comando se o sistema retornar com ENOEXEC upon execve()
, então, se você tentar executar um comando para a arquitetura errada, é melhor obter umnão executará um binárioerro de arquivo do seu shell do que o shell tentando entendê-lo como um script de shell.
Isso é permitido pelo POSIX:
Se o arquivo executável não for um arquivo de texto, o shell pode ignorar a execução deste comando.
Que na próxima revisão da normaserá alterado para:
O shell pode aplicar uma verificação heurística para determinar se o arquivo a ser executado pode ser um script e pode ignorar a execução deste comando se determinar que o arquivo não pode ser um script. Neste caso, ele escreverá uma mensagem de erro e retornará um status de saída 126.
Nota: Uma heurística comum para rejeitar arquivos que não podem ser um script é localizar um byte NUL antes de um byte <nova linha> dentro de um byte de comprimento fixo. prefixo do arquivo. Como sh é necessário para aceitar arquivos de entrada com comprimentos de linha ilimitados, a verificação heurística não pode ser baseada no comprimento da linha.
Esse comportamento pode atrapalhar os arquivos autoextraíveis do shell, que contêm um cabeçalho do shell seguido por dados binários¹.
O zsh
shell suporta NUL em sua entrada, mas observe que NULs não podem ser passados nos argumentos de execve()
, portanto você só pode usá-los no argumento ou nos nomes deconstruídas emcomandos ou funções:
$ printf '\0() echo zero; \0\necho \0\n' | zsh | hd
00000000 7a 65 72 6f 0a 00 0a |zero...|
00000007
(aqui definindo e chamando uma função com NUL como nome e passando um caractere NUL como argumento para o echo
comando interno).
Alguns irão despi-los, o que também é uma coisa sensata a fazer. NUL
s às vezes são usados como preenchimento. Eles são ignorados pelos terminais, por exemplo (às vezes eram enviados aos terminais para dar-lhes tempo para processar sequências de controle complexas (como retorno de carro (literalmente)).Buracos nos arquivos aparecem preenchidos com NULs, etc.
Observe que o não texto não está limitado a bytes NUL. Também é uma sequência de bytes que não forma caracteres válidos no código do idioma. Por exemplo, o valor do byte 0xc1 não pode ocorrer em texto codificado em UTF-8. Portanto, em localidades que usam UTF-8 como codificação de caracteres, um arquivo que contém tal byte não é um arquivo de texto válido e, portanto, não é um sh
script válido³.
Na prática, yash
é o único shell que conheço que reclamará dessas entradas inválidas.
¹ Na próxima revisão da norma,isso vai mudarpara
O arquivo de entrada pode ser de qualquer tipo, mas a parte inicial do arquivo destinada a ser analisada de acordo com a gramática do shell (XREF para XSH 2.10.2 Regras de gramática do shell) deve consistir em caracteres e não deve conter o caractere NUL. O shell não deve impor nenhum limite de comprimento de linha.
exigir explicitamente que os shells suportem entradas que comecem com uma seção sintaticamente válida sem bytes NUL, mesmo que o restante contenha NULs, para dar conta de arquivos autoextraíveis.
² e os caracteres devem ser decodificados de acordo com a codificação de caracteres do código do idioma (veja a saída de locale charmap
), e no sistema POSIX, o caractere NUL (cuja codificação é sempre o byte 0) é o único caractere cuja codificação contém o byte 0. Em outros palavras, UTF-16 não está entre as codificações de caracteres que podem ser usadas em um código de idioma POSIX.
³ No entanto, há a questão da mudança de localidade dentro do script (como quando as variáveis LANG
/ LC_CTYPE
/ LC_ALL
/ LOCPATH
são atribuídas) e em que ponto a mudança entra em vigor para o shell que interpreta a entrada.
Responder2
A razão para esse comportamento é um pouco complexa...
Primeiro, os shells modernos incluem uma verificação de arquivos potencialmente binários (que contêm bytes nulos), mas essa verificação verifica apenas a primeira linha do arquivo. É por isso que o '#' na primeira linha muda o comportamento. O Bourne Shell histórico não possui essa verificação binária e nem precisa do '#' para se comportar da maneira que você mencionou.
Em seguida, o método específico usado pelo Bourne Shell para suportar caracteres de vários bytes mbtowc()
simplesmente ignora todos os bytes nulos porque mbtowc()
retorna o comprimento do caractere 0 para um byte nulo e isso causa um loop para tentar novamente o próximo caractere.
O Bourne Shell introduziu esse tipo de código por volta de 1988 e pode ser que outros shells tenham copiado o comportamento.