Shebang: использовать интерпретатор относительно пути script

Я пытаюсь создать скрипты, которые работают везде и всегда. Для этого я использую настраиваемый python, который всегда находится в родительском каталоге по отношению к script.

Таким образом, я мог бы загрузить свой пакет на USB-накопитель, и он будет работать везде, независимо от того, где установлен палочка и установлен ли python или нет.

Однако, когда я использую

#!../python

то он работает только тогда, когда script вызывается из его каталога, что, конечно, неприемлемо.

Есть ли способ сделать это или это невозможно в текущем механизме shebang?

Ответы

Ответ 1

На этой странице имеется множество многострочных сценариев Шебанга для многих языков, например:

#!/bin/sh
"exec" "'dirname $0'/python" "$0" "[email protected]"
print copyright

И если вам нужен однострочный шебанг, этот ответ (и вопрос) подробно объясняет проблему и предлагает следующие подходы с использованием дополнительных сценариев внутри шебанга:

Использование AWK

#!/usr/bin/awk BEGIN{a=ARGV[1];sub(/[a-z_.]+$/,"python",a);system(a"\t"ARGV[1])}

Использование Perl

#!/usr/bin/perl -e$_=$ARGV[0];exec(s/\w+$/python/r,$_)

Ответ 2

Расширьте ответ @Anton, чтобы не пропустить пробелы и другие специальные символы в пути, а также немного подробнее рассказать о магии.

#!/bin/sh
"true" '''\'
exec "$(dirname "$(readlink -f "$0")")"/venv/bin/python "$0" "[email protected]"
'''

__doc__ = """You will need to deliberately set your docstrings though"""

print("This script is interpretable by python and sh!")

Этот умный скрипт понятен как sh, так и python. Каждый из которых реагирует на это по-своему. Первые 2 строки после shebang интерпретируются sh и заставляют его передавать exec в относительный двоичный файл python (поставляется с такими же аргументами командной строки). Эти же строки безопасно отбрасываются python, поскольку они составляют строку ("true"), за которой следует многострочная строка ('' ').

Еще немного об этом можно прочитать здесь.

Ответ 3

Для других, кто находит этот вопрос, ища портативное решение для строки hashbang python и найти команду AWK выше, не работает с передачей нескольких аргументов, используйте вместо этого следующее.

#!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i<ARGC;i++){b=b" \""ARGV[i]"\"";}sub(/[a-z_.\-]+$/,"python",a);system(a"\t"b)}

Чтобы изменить все строки hashbang скриптов в текущем каталоге, вы можете выполнить следующее.

sed -i '1 s|^#!.*|#!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i<ARGC;i++){b=b" \""ARGV[i]"\"";}sub(/[a-z_.\-]+$/,"python3.5",a);system(a"\\t"b)}|' *

Ответ 4

Посмотрев на эти ответы, я решил использовать специфичное для Python решение, которое фактически не меняет шебанга.

Это решение использует системный интерпретатор python, чтобы найти требуемый интерпретатор и выполнить его. Это полезно для меня, потому что позволяет мне изменять переменные окружения, а также обеспечивать правильный интерпретатор.

Поскольку он использует exec, он не удваивает память use--, новый интерпретатор заменяет последний. Кроме того, статус выхода и сигналы будут обрабатываться правильно.

Это модуль python, который загружается скриптом, который должен работать в этой среде. Импортирование этого модуля имеет побочный эффект запуска нового интерпретатора при необходимости. Как правило, модули не должны иметь побочных эффектов, но альтернативой является запуск функции модуля перед выполнением несистемного импорта, что является нарушением PEP8. Таким образом, вы должны забрать свой яд.

"""Ensure that the desired python environment is running."""


import os
import sys


def ensure_interpreter():
    """Ensure we are running under the correct interpreter and environ."""
    abs_dir = os.path.dirname(os.path.abspath(__file__))
    project_root = os.path.normpath(os.path.join(abs_dir, '../../../'))
    desired_interpreter = os.path.join(project_root, 'bin/python')

    if os.path.abspath(sys.executable) == desired_interpreter:
        return

    env = dict(os.environ)

    def prefix_paths(key, prefix_paths):
        """Add prefix paths, relative to the project root."""
        new_paths = [os.path.join(project_root, p) for p in prefix_paths]
        new_paths.extend(env.get(key, '').split(':'))
        env[key] = ':'.join(new_paths)

    prefix_paths('PYTHONPATH', ['dir1', 'dir2'])
    prefix_paths('LD_LIBRARY_PATH', ['lib'])
    prefix_paths('PYTHON_EGG_CACHE', ['var/.python-eggs'])
    env['PROJECT_ROOT'] = project_root
    os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env)


ensure_interpreter()

Если вам не нужно разбирать какие-либо переменные окружения, вы можете удалить все между env = dict(os.environ) и os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env).