Как это #! шебанг работа? - PullRequest
42 голосов
/ 09 июня 2010

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

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

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

Мне действительно любопытно, поэтому чем глубже будет ответ, тем лучше.

Ответы [ 3 ]

40 голосов
/ 09 июня 2010

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

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

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

8 голосов
/ 09 июня 2010

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


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

#!/bin/sh

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

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

> ./foo.sh

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

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

/bin/sh затемначинает чтение содержимого файла и обнаруживает шебанг (#!) в самом начале файла.Для оболочки это токен («магическое число»), по которому он знает, что файл содержит скрипт.

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

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

6 голосов

Системный вызов ядра 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), который читает файл elf, помещает его в память правильно, и начинает новый процесс с ним. См. Также: Как ядро ​​получает исполняемый двоичный файл, работающий под Linux?

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

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

...