
Потому что именно это некоторые из них и делают.
> 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^@!^@
^@
Это на самом деле какая-то проблема совместимости?необходимыйпо стандарту? Потому что это выглядит довольно опасно и неожиданно.
решение1
СогласноPOSIX,
Входной файл должен быть текстовым файлом, за исключением того, что длина строк не должна быть ограничена.¹
Символы NUL² на входесделать это нетекстовым, поэтому поведение не определено с точки зрения POSIX, поэтому sh
реализации могут делать все, что захотят (и совместимый с POSIXсценарийне должны содержать NUL).
Некоторые оболочки сканируют первые несколько байтов на наличие нулей и отказываются запускать скрипт, предполагая, что вы по ошибке попытались выполнить файл, не являющийся скриптом.
Это полезно, потому что exec*p()
функции, env
команды, sh
... find -exec
являютсянеобходимыйдля вызова оболочки для интерпретации команды, если система возвращает ENOEXEC при execve()
, поэтому, если вы пытаетесь выполнить команду для неправильной архитектуры, лучше получитьне будет выполнять двоичный файлфайл error из вашей оболочки, чем оболочка пытается осмыслить его как скрипт оболочки.
Это разрешено POSIX:
Если исполняемый файл не является текстовым, оболочка может обойти выполнение этой команды.
Что в следующей редакции стандартабудет изменено на:
Оболочка может применить эвристическую проверку, чтобы определить, может ли исполняемый файл быть скриптом, и может обойти выполнение этой команды, если определит, что файл не может быть скриптом. В этом случае она выведет сообщение об ошибке и вернет код выхода 126.
Примечание: обычная эвристика для отклонения файлов, которые не могут быть скриптом, заключается в поиске байта NUL перед байтом <newline> в префиксе файла фиксированной длины. Поскольку sh должен принимать входные файлы с неограниченной длиной строки, эвристическая проверка не может основываться на длине строки.
Однако такое поведение может помешать работе самораспаковывающихся архивов оболочки, которые содержат заголовок оболочки, за которым следуют двоичные данные¹.
Оболочка zsh
поддерживает NUL на входе, однако следует отметить, что NUL нельзя передавать в аргументах execve()
, поэтому вы можете использовать его только в аргументах или именахвстроенныйкоманды или функции:
$ printf '\0() echo zero; \0\necho \0\n' | zsh | hd
00000000 7a 65 72 6f 0a 00 0a |zero...|
00000007
(здесь определяется и вызывается функция с именем NUL и передается символ NUL в качестве аргумента встроенной echo
команде).
Некоторые их удаляют, что также разумно. NUL
Иногда s используются в качестве заполнения. Например, они игнорируются терминалами (иногда они отправлялись на терминалы, чтобы дать им время для обработки сложных управляющих последовательностей (вроде возврата каретки (буквально)). Дыры в файлах выглядят заполненными NUL и т. д.
Обратите внимание, что нетекст не ограничивается байтами NUL. Это также последовательность байтов, которые не образуют допустимые символы в локали. Например, значение байта 0xc1 не может встречаться в тексте в кодировке UTF-8. Поэтому в локалях, использующих UTF-8 в качестве кодировки символов, файл, содержащий такой байт, не является допустимым текстовым файлом и, следовательно, не является допустимым sh
скриптом³.
На практике yash
это единственная известная мне оболочка, которая будет жаловаться на такой недопустимый ввод.
¹ В следующей редакции стандартаэто изменитсяк
Входной файл может быть любого типа, но начальная часть файла, предназначенная для анализа в соответствии с грамматикой оболочки (XREF на XSH 2.10.2 Shell Grammar Rules), должна состоять из символов и не должна содержать символ NUL. Оболочка не должна накладывать никаких ограничений на длину строки.
явно требуя от оболочек поддерживать ввод, начинающийся с синтаксически допустимого раздела без байтов NUL, даже если остальная часть содержит байты NUL, для учета самораспаковывающихся архивов.
² и символы должны декодироваться в соответствии с кодировкой символов локали (см. вывод locale charmap
), а в системе POSIX символ NUL (кодировка которого всегда представляет собой байт 0) является единственным символом, кодировка которого содержит байт 0. Другими словами, UTF-16 не входит в число кодировок символов, которые можно использовать в локали POSIX.
³ Однако возникает вопрос об изменении локали внутри скрипта (например, при назначении переменных LANG
/ LC_CTYPE
/ LC_ALL
/ LOCPATH
) и о том, в какой момент изменение вступает в силу для оболочки, интерпретирующей ввод.
решение2
Причина такого поведения немного сложна...
Во-первых, современные оболочки включают проверку на потенциально двоичные файлы (содержащие нулевые байты), но эта проверка проверяет только первую строку файла. Вот почему '#' в первой строке меняет поведение. Историческая оболочка Bourne Shell не имеет такой двоичной проверки и даже не нуждается в '#', чтобы вести себя так, как вы упомянули.
Затем специальный метод, используемый оболочкой Bourne Shell для поддержки многобайтовых символов, mbtowc()
просто пропускает все нулевые байты, поскольку mbtowc()
возвращает длину символа 0 для нулевого байта, и это приводит к циклу повторной попытки ввода следующего символа.
Bourne Shell представила этот тип кода примерно в 1988 году, и вполне возможно, что другие оболочки скопировали это поведение.