Рубрика:
Карьера/Образование /
Пятая пара
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
ДЕНИС СИЛАКОВ, кандидат ф.-м. н., ЗАО «РОСА», член рабочей группы LSB, старший архитектор, занимается автоматизацией разработки ОС «РОСА», denis.silakov@rosalab.ru
Shebang – уроки истории
Все знают, что скрипты в UNIX принято начинать с символов #!, но не все догадываются, почему. Оказывается, это довольно поучительная история!
Известно, что учиться лучше всего на ошибках, причем на чужих. Но изучение удачных решений тоже очень полезно для повышения квалификации. Для разработчиков ИТ-систем хорошие примеры обоих видов предоставляет история shebang – такое имя получило сочетание символов решетки и восклицательного знака (#!), с которого начинаются скрипты в UNIX-подобных ОС.
Предыстория и постановка задачи
С точки зрения пользователя, одна из основных задач ОС – запускать и выполнять различные приложения. Однако что значит «запустить программу»? Для пользователей современных сред с графическим интерфейсом это означает кликнуть на иконку программы или выбрать соответствующий пункт в меню «Пуск» либо его аналогах. Во времена, когда возникла наша проблема, графический интерфейс пользователя оставался делом светлого будущего, а программы запускались из командной строки. При таком способе работы пользователь взаимодействует с интерактивной командной оболочкой – специальной программой, которая принимает вводимые с клавиатуры команды и выполняет их – либо самостоятельно, либо с помощью ядра ОС и других программ. Для запуска программы необходимо ввести имя запускаемого файла и нажать <Enter>.
Но все это лишь верхушка айсберга. Давайте посмотрим, что происходит «под капотом» (в оболочке, системных библиотеках и ядре ОС) после клика на иконку программы или ввода ее имени в командной строке.
В классическом случае двоичных скомпилированных программ процесс запуска подразумевает загрузку кода программы и ее данных в оперативную память и передачу управления в точку входа программы. Всем этим занимается ядро ОС, а для удобства взаимодействия с ядром часто предоставляются дополнительные системные библиотеки. Ядра и библиотеки большинства ОС (и нынешних, и сорокалетней давности) для запуска программ предоставляют набор системных вызовов exec(), которым в качестве аргумента передается имя файла программы. Эти функции уже много лет определяются стандартом POSIX.
Однако программы бывают не только бинарные. С первых версий UNIX оболочка Bourne Shell, с которой взаимодействовали операторы ЭВМ, предоставляла возможность создания скриптов на специальном языке. Язык этот так и назвали – язык Shell. Подобная функциональность стала популярна (и остается таковой по сей день) для решения повседневных рутинных задач как программистов, так и администраторов. Как оказалось, справиться со многими задачами можно скриптом в несколько строчек на языке Shell, причем скрипт не надо перекомпилировать при каждом изменении и держать для этого в системе компилятор.
Изначально для запуска скрипта с помощью интерпретатора необходимо было явно вызвать этот интерпретатор, передав ему имя скрипта в качестве аргумента. Например, в случае Shell, интерпретатор – это программа /bin/sh. Но можно указать просто sh, поскольку директория /bin традиционно включена в переменную PATH и просматривается при попытке определить местоположение запрошенной программы:
$ sh ./script.sh
Однако пользователи и администраторы – народ ленивый, а лень, как известно, – двигатель прогресса. И вполне естественно, что возникло желание запускать Shell-скрипты как обычные программы, то есть просто вводя их имя и нажимая <Enter>.
Первая реализация
Первая реализация такой возможности не заставила себя долго ждать и была достаточно примитивной – оболочка Bourne Shell в первую очередь пыталась «скормить» запускаемый файл ядру ОС. Если же ядро возвращало ошибку NOEXEC, означающую, что ему не удалось распознать формат файла, то оболочка пыталась передать файл интерпретатору sh, например, с помощью вызова execl(), который в случае успеха замещает исходный процесс новым:
execl(program, basename(program), (char *)0);
if (errno == ENOEXEC)
execl ("/bin/sh", "sh", "-c", program, (char *)0);
perror(program);
return -1;
На первых порах такой реализации было достаточно. Но по мере развития UNIX и компонентов ОС выяснилось, что такой прямолинейный подход хотя и прост в реализации, но не отличается масштабируемостью и универсальностью.
Статью целиком читайте в журнале «Системный администратор», №12 за 2013 г. на страницах 82-85.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|