Выполнение скриптов Python с помощью Shebang
Когда вы читаете чужой Python код, то часто видите загадочную строку, которая всегда появляется вверху файла и начинается с характерной последовательности shebang #!
. Она выглядит как не очень полезный комментарий, но в остальном непохожа ни на что другое, что вы узнали о Python. И заставляет задуматься, что это такое и зачем оно здесь. Как будто этого недостаточно, чтобы сбить вас с толку, строка shebang появляется только в некоторых модулях Python.
В этом руководстве вы:
- Узнаете, что такое shebang.
- Решите, когда включать shebang в скрипты Python.
- Определите shebang портативным способом в разных системах.
- Передадите аргументы команде, определённые в shebang.
- Узнаете ограничения shebang и некоторые его альтернативы.
- Выполните скрипты через собственный интерпретатор написанный на Python.
Для продолжения, вы должны иметь базовые знания о командной строке и знать как запускать из неё скрипты Python.
Что такое shebang и когда его следует использовать
Короче говоря, shebang — это комментарий особого типа, который вы можете включать в исходный код, чтобы указать оболочке операционной системы, где найти интерпретатор для остальной части файла:
#!/usr/bin/python3
print("Hello, World!")
Если вы используете shebang, он должен размещаться в первой строке вашего скрипта и должен начинаться со знака решётки #
, за которым следует восклицательный знак !
, известный как bang
, отсюда и название shebang. Выбор знака решётки для начала этой специальной последовательности символов не случаен, поскольку многие языки сценариев используют его для встроенных комментариев.
Вы должны убедиться, что не ставите никаких других комментариев пред строкой shebang, если вы хотите, чтобы она работала правильно, иначе она не будет распознана! После восклицательного знака укажите абсолютный путь к соответствующему интерпретатору кода, например Python. Предоставление относительного пути, к сожалению, не будет иметь никакого эффекта.
Примечание. Shebang распознаётся только оболочками, такими как Z shell или Bash, работающими в Unix-подобных системах, включая дистрибутивы macOs и Linux. Они не имеют особого значения в терминале Windows, который обрабатывает shebang как обычный комментарий, игнорируя их.
Вы можете заставить shebang работать в Windows, установив Windows Subsystem for Linux (WSL), которая поставляется с оболочкой Unix. В качестве альтернативы, Windows позволяет создать глобальную файловую ассоциацию между расширением файла, например .py
и программной, например интерпретатором Python, для достижения аналогичного эффекта.
Нередко shebang сочетается с идиомой name-main
, что предотвращает запуск основного блока кода, когда кто-то импортирует файл из другого модуля:
#!/usr/bin/python3
if __name__ == "__main__":
print("Hello, World!")
С этим условным оператором Python будет вызывать функцию print()
только тогда, когда вы запускаете этот модуль непосредственно как скрипт — например, указав его путь к интерпретатору Python:
$ python3 /path/to/your/script.py
Hello, World!
Пока содержимое сценария начинается с правильно определённой строки shebang, а пользователь вашей системы имеет разрешение на выполнение соответствующего файла, вы можете опустить команду python3 для запуска этого сценария:
$ /path/to/your/script.py
Hello, World!
Shebang имеет отношение только к исполняемым сценариям, которые вы хотите выполнять без явного указания программы для их запуска. Обычно вы не помещаете shebang в модуль Python, который содержит только определении функций и классов, предназначенные для импорта из других модулей. Поэтому используйте shebang, если вы не хотите ставить перед командой, которая запускает ваш скрипт Python, префикс python
или python3
.
Примечание. В старые времена Python, строка shebang иногда появлялась рядом с другим специально отформатированным комментарием, описанным в PEP 263:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
if __name__ == "__main__":
print("Grüß Gott")
Раньше выделенная строка была необходима, чтобы сообщить интерпретатору, какую кодировку символом он должен использовать для правильного чтения исходного кода, поскольку Python по умолчанию использовал ASCII. Однако это было важно только тогда, когда вы напрямую встраивали нелатинские символы, такие как ü
или ß
, в свой код.
Этот специальный комментарий сегодня не актуален, потому что современные версии Python используют универсальную кодировку UTF-8, которая легко обрабатывает такие символы. Тем не менее всегда предпочтительнее заменять сложные символы их закодированными представлениями с использованием литералов Unicode.
>>> "Grüß Gott".encode("unicode_escape")
b'Gr\\xfc\\xdf Gott'
Ваши иностранные коллеги, у которых другая раскладка клавиатуры, будут вам за это благодарны!
Теперь, когда у вас есть общее представление, что такое shebang и когда его использовать, вы готовы изучить его более подробно. В следующем разделе мы более подробно рассмотрим, как это работает.
Как shebang работает
Обычно для запуска программы в терминале необходимо указать полный путь к конкретному исполняемому файлу или имя команды, присутствующей в одном из каталогов, перечисленных в переменной среды PATH
. Один или несколько аргументов командной строки могут следовать этому пути или команде:
$ /usr/bin/python3 -c 'print("Hello, World!")'
Hello, World!
$ python3 -c 'print("Hello, World!")'
Hello, World!
Здесь мы запускаем интерпретатор Python в не интерактивном режиме для однострочной программы, переданной с помощью параметра -c
. В первом случае указываем абсолютный путь к python3
, а во втором случае полаемся на тот факт, что родительская папка /usr/bin
включена в путь поиска по умолчанию. Ваша оболочка может найти исполняемый файл Python, даже если не указать полный путь, просматривая каталоги в переменной PATH
.
Примечание: Если несколько команд с одним и тем же именем существуют в более чем одном каталоге, указанном в PATH
, оболочка выполнит первую, которую сможет найти. В результате результат выполнения команды без явного указания соответствующего пути иногда может быть неожиданным. Это будет зависеть от порядка каталогов в переменной PATH
. Однако это может быть полезно, как вы узнаете позже.
На практике, большинство ваших программ на Python будут состоять из более чем одной строки кода, распределённого по нескольким модулям. Обычно в вашей программе будет единственная работоспособная точка входа: скрипт, который вы можете передать интерпретатору Python для выполнения:
$ python3 /path/to/your/script.py
Hello, World!
Пока в этом вызове нет ничего удивительного, потому что вы видели его раньше. Однако обратите внимание, что вы по-прежнему запускаете двоичный исполняемый файл, содержащий машинный код для вашей платформы и компьютерной архитектуры, который, в свою очередь, интерпретирует Python:
$ hexdump -C /usr/bin/python3 | head
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 00 aa 5f 00 00 00 00 00 |..>......._.....|
00000020 40 00 00 00 00 00 00 00 38 cf 53 00 00 00 00 00 |@.......8.S.....|
00000030 00 00 00 00 40 00 38 00 0d 00 40 00 20 00 1f 00 |....@.8...@. ...|
00000040 06 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000050 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 |@.@.....@.@.....|
00000060 d8 02 00 00 00 00 00 00 d8 02 00 00 00 00 00 00 |................|
00000070 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 |................|
00000080 18 03 00 00 00 00 00 00 18 03 40 00 00 00 00 00 |..........@.....|
00000090 18 03 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |..@.............|
Во многих дистрибутивах Linux python3
— это псевдоним исполняемого файла скомпилированного в ELF, который вы можете посмотреть с помощью команды hexdump
.
Однако ваша оболочка также может выполнять сценарии или текстовые файлы, содержащие исходный код, выраженный на интерпретируемом языке высокого уровня, таком как Python, Perk или JavaScript. Поскольку выполнение сценариев потенциально может иметь вредные побочные эффекты, особенно если они исходят из ненадёжных источников, файлы по умолчанию не являются исполняемыми. Когда вы попытаетесь запустить скрипт Python, не сделав его сначала исполняемым, вы увидите это сообщение об ошибке в терминале:
$ ./script.py
bash: ./script.py: Permission denied
Как правило, вы можете дать разрешение на выполнение указанного файла его владельцу, пользователю, принадлежащему к группе пользователей, связанной с файлом, или всем остальным. Чтобы позволить любому выполнить ваш скрипт, вы можете изменить биты режима файла с помощью команды chmod
:
chmod +x script.py
Разрешения файла Unix следуют символической нотации, где буква x
обозначает разрешение на выполнение, а знак плюс +
включает соответствующий бит. На некоторых терминалах это также изменит цвет, используемый для отображения исполняемых скриптов, чтобы их можно было различить с первого взгляда.
Хотя сейчас можно запустить скрипт, он всё равно не будет работать так, как планировалось:
$ ./script.py
./script.py: line 1: syntax error near unexpected token `"Hello, World!"'
./script.py: line 1: `print("Hello, World!")'
Если не включить shebang в начале файла, оболочка будет считать, что сценарий написан на соответствующем языке оболочки. Например, если вы используете оболочку Bash, то она будет ожидать, что в вашем файле будут найдены команды Bash. Итак, когда он натыкается на вызов функции Python print()
в скрипте, то не понимает его. Обратите внимание, что расширение файла, например .py
, совершенной не имеет значения!
Только когда вы указываете абсолютный путь к вашему интерпретатору Python, используя shebang в скрипте, оболочка будет знать, куда передать этот скрипт:
$ cat script.py
#!/usr/bin/python3
print("Hello, World!")
$ ./script.py
Hello, World!
Это очень удобно, потому что теперь вы можете создавать исполняемые сценарии Python. К сожалению, жёстко закодированный абсолютный путь в shebang не очень переносим между системами, даже в рамках семейства Unix. Что, если Python был установлен в другом месте или команда python3
была заменена на python
? Как насчёт использования виртуальной среды или pyenv
? В настоящее время вы всегда будете запускать скрипт через интерпретатор Python по умолчанию в операционной системе.
В следующем разделе мы рассмотрим возможность решения этих проблем, улучшив свой shebang и изучив некоторые альтернативы.
Как задать переносимый shebang
Наличие фиксированного абсолютного пути в shebang означает, что ваш скрипт может работать не на всех системах, потому что могут быть небольшие различия.
Помните, что вы не можете указать относительный путь в shebang, так как он всегда должен быть абсолютным. Из-за этого ограничения многие разработчики приняли обходной путь, используя команду /usr/bin/env
, которая может определить фактический путь к интерпретатору Python:
#!/usr/bin/env python3
print("Hello, World!")
При вызове без каких-либо аргументов команда /usr/bin/env
отобразит переменные среды, определённые в оболочке. Её основная цель, состоит в том, чтобы запустить программу в изменённой среде, позволяя вам временно переопределить определённые переменные. Например, вы можете изменить язык данной программы, задав для него переменную LANG
:
$ /usr/bin/env LANG=es_ES.UTF_8 git status
En la rama master
Tu rama está actualizada con 'origin/master'.
nada para hacer commit, el árbol de trabajo está limpio
Команда git status
обычно отображает эти сообщения на вашем языке по умолчанию, но здесь вы запрашиваете испанский язык. Обратите внимание, что не каждая команда поддерживает несколько языков, и может потребоваться сначала установить дополнительный языковой пакет в операционную систему, чтобы он вступил в силу.
Преимущество /usr/bin/env
в том, что вам не нужно менять какие-либо переменные окружения для запуска команды:
$ /usr/bin/env python3
Python 3.11.2 (main, Feb 13 2023, 19:48:40) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
В качестве побочного эффекта он найдёт первое вхождение указанного исполняемого файла, например python3
, в переменной PATH
и запустит его. Это очень полезно, если учесть, что активация виртуальной среды Python изменяет переменную PATH
в текущем сеансе терминала, добавляя родительскую папку исполняемого файла Python в активную виртуальную среду:
$ which python3
/usr/bin/python3
$ python -m venv venv/
$ source venv/bin/activate
(venv) $ which python3
/home/realpython/venv/bin/python3
(venv) $ echo $PATH
/home/realpython/venv/bin
⮑:/home/realpython/.local/bin:/usr/local/sbin:/usr/local/bin
⮑:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
⮑:/snap/bin:/home/realpython/.local/bin:
Когда в оболочке нет активной виртуальной среды, команда python3
является сокращением /usr/bin/python3
. Но как только создаёте и активируете новую виртуальную среду, та же самая команда указывает на исполняемый файл Python в локальной папке venv/
. Вы можете понять почему это происходит, проверив переменную PATH
которая теперь начинается с этой папки и имеет приоритет над глобально установленным интерпретатор Python.
Примечание: Никогда не используйте команду python
в shebang, потому что она может сопоставиться либо с python2
, либо с python3
, в зависимости от операционной системы и её конфигурации. Единственным исключением может быть ситуация, когда вы написали сценарий с обратной совместимостью и хотели, чтобы он выполнялся в обеих версиях Python. Вы можете больше узнать о рекомендуемых практиках в PEP 394
Одним из недостатков /usr/bin/env
является то, что он не позволяет передавать какие-либо аргументы базовой команде из коробки. Итак, если вы хотите запустить python3 -i
, чтобы интерпретатор Python продолжал работать в интерактивном режиме после завершения вашего скрипта, тогда это приведёт к поломке:
$ ./script.py
/usr/bin/env: ‘python3 -i’: No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines
К счастью, для этого есть быстрое решение, на которое намекает сообщение об ошибке. Вы можете использовать параметр -S
команды /usr/bin/env
, чтобы разделить следующую строку на отдельные аргументы, передаваемые интерпретатору:
#!/usr/bin/env -S python3 -i
print("Hello, World!")
После запуска скрипта вы попадёте в интерактивный Python REPL, чтобы могли проверить состояние переменных, что может быть полезно при пост-мортемной отладке.
Примечание: В некоторых системах строка shebang может быть ограничена определённым количеством символов, поэтому следите за тем, чтобы её длинна была разумной.
В конце концов, shebang — это относительно простой способ создания исполняемых Python-скриптов, но он требует определённого уровня знаний об оболочке, переменных среды и операционной системе, с которой вы работаете. Кроме того, он не идеален с точки зрения переносимости, поскольку в основном работает на Unix-подобных операционных системах.
Если вы не хотите настраивать shebang самостоятельно, можете положиться на такие инструменты, как setuptools
или Poetry
, которые сделают всю работу за вас. Они позволяют настраивать удобные точки входа в ваш проект через обычные функции. В качестве альтернативы вы можете создать специальный файл __main__.py
, чтобы превратить ваш модуль в Python в исполняемый модуль, или вы можете создать исполняемое ZIP-приложение на Python, избегая необходимости использовать shebang.
Это достойные альтернативы shebang, и они позволяют избежать некоторых его недостатков.
Примеры использования shebang
До сих пор вы использовали shebang, чтобы указать конкретную версию интерпретатора Python для ваших скриптов. Однако в некоторых случаях может существовать более одного интерпретатора, способного понимать один и тот же код, и работать с ним. Например, вы можете написать скрипт, совместимый с Python 2 и Python 3 одновременно:
$ /usr/bin/env python2 script.py
Hello, World!
$ /usr/bin/env python3 script.py
Hello, World!
Круглые скобки вокруг оператора print
в Python 2 в конечном итоге игнорируются, поэтому вы можете безопасно использовать команду python
без явной версии в вашем shebang, если придерживаетесь консервативного синтаксиса:
#!/usr/bin/env python
print("Hello, World!")
Вы даже можете запустить этот код через интерпретатор Perl, который имеет функцию print
, ведущую себя так же, как её Python аналог:
#!/usr/bin/env perl
print("Hello, World!")
Вам даже не нужно менять расширение файла, на него оболочке всё равно. Просто обновив строку shebang, вы сможете запустить этот скрипт с Perl, если ранее установили его интерпретатор.
$ ./script.py
Hello, World!$
Результат выполнения этого скрипта выглядит почти также, за исключением того, что Python добавляет завершающую новую строку, а Perl — нет. Это простой пример программы-полиглота, написанной таким образом, что несколько языков программирования могут её понимать и выдавать одинаковый результат.
Правильная версия программы Hello, World!
, написанная на perl, может выглядеть примерно так:
#!/usr/bin/env perl
print("Hello, World!\n");
Использование круглых скобок вокруг аргументов функции в Perl считается хорошей практикой, но не является строго обязательным. Обратите внимание на символ новой строки (\n
) в строковом литерале и точку с запятой (;
) в конце строки, которая завершает оператор.
Если на вашем компьютере установлен Node.js, вы можете запускать JavaScript прямо из своего терминала. Вот аналог скрипта Hello, World!
, написанный на языке, который раньше был достоянием веб-браузеров:
#!/usr/bin/env node
console.log("Hello, World!")
Несмотря на то, что знак решётки #
не является допустимым синтаксисом в JavaScript, Node.js распознаёт отличительную последовательность shebang и игнорирует всю строку перед выполнением остальной части файла.
Примечание: Если вы знакомы с JavaScript, то возможно привыкли завершать каждое выражение точкой с запятой. Тем не менее это вопрос стиля, поскольку официальных рекомендаций по этому поводу нет, JavaScript поставляется с механизмом автоматической вставки точки с запятой — ASI. Некоторые компании опускают точку с запятой, в то время как другие включают их в каждую строку — например, GitHub и Spotify соответственно.
Приложив немного усилий, вы даже сможете писать сценарии на языке Java, который технически не является языком сценариев. Java требует компиляции своего высокоуровневого кода в байт-код для виртуальной машины Java (JVM), которая похожа на интерпретатор Python, но для двоичных кодов операций.
Чтобы сделать shebang возможным с программами Java, вы должны выполнить следующие шаги:
- Убедиться, что файл с исходным кодом Java, не имеет традиционного расширения
.java
. Например, вы можете присвоить файлу нейтральное расширение.j
. Как объясняется в этом ответе на StackOverflow, это гарантирует, что Java игнорирует недопустимый символ хэша#
. - Запустите исходный файл с помощью команды
java
вместоjavac
. - Явно установите версию Java 11 с помощью опции
--source
.
Вот полный пример такого Java-скрипта
:
#!/usr/bin/env -S java --source 11
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Запуск этого скрипта занимает значительно больше времени, чем аналогичный Hello, World!
написанный на интерпретируемых языках. Это связано с дополнительным этапом компиляции, который выполняется на лету при каждом вызове.
Примечание: Фреймворка Spring Boot может искусно добавить shebang вместе со сценарием оболочки в начало двоичного архива с вашими классами Java, чтобы создать полностью исполняемый файл JAR для Unix-подобных систем. Это упрощает развёртывание таких веб-приложений Java!
Каковы лучшие практики для shebang
Подводя итого, вот несколько правил, которым вы должны следовать, чтобы успешно использовать shebang в сценариях Python:
- Помните, что shebang применим только к исполняемым сценариям в Unix-подобных операционных системах.
- Если вы не укажите shebang, ваша оболочка попытается интерпретировать сценарий так, как если бы он был написан на соответствующем языке оболочки.
- Не размещайте shebang в простых модулях Python, которые предназначены только для импорта, но не выполнения.
- Убедитесь, что ваш скрипт сохранён в исполняемый файл (у файла и пользователя есть права на выполнение сценария).
- Попробуйте объединить shebang с идиомой
name-main
(if __name__ == "__main__":)
. - Начинайте свой скрипт со строки shebang и не размещайте перед ней никаких других комментариев.
- Начинайте shebang с последовательности символов
#!
, чтобы отличить его от стандартного комментария. - Используйте команду
/usr/bin/env python3
, чтобы избежать жёсткого кодирования абсолютного пути к какому-либо конкретному интерпретатору Python. - Избегайте использования команды
python
, если только ваш скрипт не обратно совместим с Python 2. Как правило, вы должны использовать более явныйpython3
. - Используйте флаг
-S
если вам нужно передать интерпретатору дополнительные аргументы, например#!/usr/bin/env -S python3 -i
. - Будьте осторожны с количеством символов, которые вставляется в строку shebang.
Наконец, спросите себя, нужно ли вам добавлять shebang вручную или его можно сгенерировать автоматически или заменить какой-либо абстракцией более высокого уровня с помощью утилиты в вашей цепочке инструментов.
Заключение
Вы знаете, что такое shebang, как работает и когда вы нужно включить его в свои скрипты Python. Вы видели, как определить переносимый shebang и передать ему аргументы. Вы также рассмотрели несколько примеров shebang. Наконец вы рассмотрели некоторые из лучших практик использования shebang в сценариях и узнали о его недостатках и альтернативах.
В этом руководстве вы научились:
- Решать, когда включать shebang в скрипты Python.
- Определять shebang переносимым способом в разных системах.
- Передавать аргументы команде, определённой в shebang.
- Ограничениям shebang и его альтернатив.
Теперь, когда вы понимаете, как использовать shebang, почему бы не попробовать его в своих сценариях? Какие ещё варианты использования shebang вы можете придумать?