Подтвердить что ты не робот

Как #! Шебанг работает?

В script вы должны включить #! в первую строку, а затем путь к программе, которая будет выполнять script (например, sh, perl).

Насколько мне известно, символ # обозначает начало комментария и эта строка должна игнорироваться программой, выполняющей script. Казалось бы, эта первая строка в какой-то момент читается чем-то для того, чтобы script выполнялась надлежащей программой.

Может ли кто-нибудь пролить больше света на работу #!?

Мне действительно интересно, так что чем глубже ответ, тем лучше.

4b9b3361

Ответ 1

Рекомендуемое чтение:

За этим отвечает за загрузчик программ для ядра unix. Когда вызывается exec(), он просит ядро ​​загрузить программу из файла по ее аргументу. Затем он проверяет первые 16 бит файла, чтобы узнать, какой у него исполняемый формат. Если он обнаружит, что эти биты #!, он будет использовать остальную часть первой строки файла, чтобы найти, какую программу он должен запустить, и он предоставляет имя файла, который он пытался запустить (script), как последний аргумент программы переводчика.

Затем интерпретатор работает как обычно и рассматривает #! как строку комментария.

Ответ 2

Рассказ: Строка shebang (#!) считывается оболочкой (например, sh, bash и т.д.) программа операционной системы погрузчик. Хотя он формально выглядит как комментарий, тот факт, что он в первых двух байтах файла отмечает весь файл как текстовый файл и как script. script будет передан исполняемому файлу, указанному в первой строке после shebang. Вуаля!


Немного длинная история: Представьте, что у вас есть script, foo.sh с установленным битом (x). Этот файл содержит, например, следующее:

#!/bin/sh

# some script commands follow...:
# *snip*

Теперь, в вашей оболочке, введите:

> ./foo.sh

Изменить: Также читайте комментарии ниже или перед тем, как прочитать следующее! Как оказалось, я ошибся. По-видимому, это не оболочка, которая передает script целевому интерпретатору, а сама операционная система (ядро).

Помните, что вы вводите это внутри процесса оболочки (предположим, что это программа /bin/sh). Следовательно, этот ввод должен быть обработан этой программой. Он интерпретирует эту строку как команду, так как обнаруживает, что самое первое, что введено в строке, - это имя файла, который фактически существует и который имеет исполняемый бит (ы).

/bin/sh затем начинает чтение содержимого файла и обнаруживает shebang (#!) в самом начале файла. Для оболочки это токен ( "магическое число" ), с помощью которого он знает, что файл содержит script.

Теперь, как он знает, какой язык программирования script написан? В конце концов, вы можете выполнить скрипты Bash, скрипты Perl, скрипты Python... Вся оболочка знает, что она ищет файл script (который не является двоичным файлом, а текстовым файлом), Таким образом, он считывает следующий ввод до первого разрыва строки (что приведет к /bin/sh, сравните с вышесказанным). Это интерпретатор, которому будет передан script для выполнения. (В данном конкретном случае целевой интерпретатор является самой оболочкой, поэтому ему не нужно вызывать новую оболочку для script, она просто обрабатывает остальную часть самого файла script.)

Если script предназначено для, например, /bin/perl, все, что должен сделать интерпретатор Perl (необязательно), - это посмотреть, действительно ли строка shebang упоминает интерпретатор Perl. Если нет, интерпретатор Perl знает, что он не может выполнить этот script. Если действительно интерпретатор Perl упоминается в строке shebang, он читает остальную часть файла script и выполняет его.

Ответ 3

Системный вызов ядра Linux exec использует начальные байты #! для определения типа файла

Когда вы делаете на Bash:

./something

в Linux это вызывает системный вызов exec с путем ./something.

Эта строка вызывается в ядре для файла, переданного exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Он читает самые первые байты файла и сравнивает их с #!.

Если сравнение верно, то остальная часть строки анализируется ядром Linux, которое делает еще один вызов exec с путем /usr/bin/env python и текущим файлом в качестве первого аргумента:

/usr/bin/env python /path/to/script.py

и это работает для любого языка сценариев, который использует # в качестве символа комментария.

И да, вы можете сделать бесконечный цикл с помощью:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash распознает ошибку:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! читается человеком, но в этом нет необходимости.

Если файл начинается с разных байтов, то системный вызов exec будет использовать другой обработчик. Другой наиболее важный встроенный обработчик для исполняемых файлов ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305, который проверяет байты 7f 45 4c 46 (который также оказывается читаемым человеком для .ELF). Давайте подтвердим это, прочитав 4 первых байта /bin/ls, который является исполняемым файлом ELF:

head -c 4 "$(which ls)" | hd 

выход:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Поэтому, когда ядро видит эти байты, оно берет файл ELF, правильно помещает его в память и запускает новый процесс с ним. Смотрите также: Как ядро получает исполняемый двоичный файл, работающий под Linux?

Наконец, вы можете добавить свои собственные обработчики shebang с помощью механизма binfmt_misc. Например, вы можете добавить пользовательский обработчик для файлов .jar. Этот механизм даже поддерживает обработчики по расширению файла. Другое приложение - для прозрачного запуска исполняемых файлов другой архитектуры с QEMU.

Я не думаю, что POSIX указывает на шебанги: https://unix.stackexchange.com/a/346214/32558, хотя в разделах с обоснованиями и в форме "если исполняемые скрипты поддерживаются системой, что-то может случиться". macOS и FreeBSD также, похоже, реализуют это.