Разрешено ли оболочкам игнорировать байты NUL в скриптах?

Разрешено ли оболочкам игнорировать байты NUL в скриптах?

Потому что именно это некоторые из них и делают.

> 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 году, и вполне возможно, что другие оболочки скопировали это поведение.

Связанный контент