Выполнение скриптов Python с помощью Shebang

Источник: «Executing Python Scripts With a Shebang»
В этом руководстве вы узнаете, когда и ка использовать строку shebang в скриптах Python для их исполнения из Unix-подобной оболочки.

Когда вы читаете чужой Python код, то часто видите загадочную строку, которая всегда появляется вверху файла и начинается с характерной последовательности shebang #!. Она выглядит как не очень полезный комментарий, но в остальном непохожа ни на что другое, что вы узнали о Python. И заставляет задуматься, что это такое и зачем оно здесь. Как будто этого недостаточно, чтобы сбить вас с толку, строка 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, вы должны выполнить следующие шаги:

  1. Убедиться, что файл с исходным кодом Java, не имеет традиционного расширения .java. Например, вы можете присвоить файлу нейтральное расширение .j. Как объясняется в этом ответе на StackOverflow, это гарантирует, что Java игнорирует недопустимый символ хэша #.
  2. Запустите исходный файл с помощью команды java вместо javac.
  3. Явно установите версию 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 вручную или его можно сгенерировать автоматически или заменить какой-либо абстракцией более высокого уровня с помощью утилиты в вашей цепочке инструментов.

Заключение

Вы знаете, что такое shebang, как работает и когда вы нужно включить его в свои скрипты Python. Вы видели, как определить переносимый shebang и передать ему аргументы. Вы также рассмотрели несколько примеров shebang. Наконец вы рассмотрели некоторые из лучших практик использования shebang в сценариях и узнали о его недостатках и альтернативах.

В этом руководстве вы научились:

Теперь, когда вы понимаете, как использовать shebang, почему бы не попробовать его в своих сценариях? Какие ещё варианты использования shebang вы можете придумать?

Дополнительные материалы

Предыдущая Статья

Vim: Сохранить всё сразу

Следующая Статья

Laravel Миграции: Как добавить индекс, если он, возможно, существует