web-dev-qa-db-ja.com

シバン:スクリプトパスに関連するインタプリタを使用する

私はいつでもどこでも機能するスクリプトを作成しようとしています。このために、私はカスタムビルドのpythonを使用します。これは、常にスクリプトに関連する親ディレクトリにあります。

このようにして、パッケージをUSBスティックにロードでき、スティックが取り付けられている場所やpythonがインストールされているかどうかに関係なく、どこでも機能します。

しかし、私が使用するとき

#!../python

その場合、スクリプトがそのディレクトリから呼び出された場合にのみ機能しますが、これはもちろん受け入れられません。

これを行う方法はありますか、それとも現在のシバンメカニズムではこれは不可能ですか?

26
Robby75

多くの言語の このページ には、複数行のShebangスクリプトの健全なセットがあります。例:

#!/bin/sh
"exec" "`dirname $0`/python" "$0" "$@"
print copyright

また、1行のShebangが必要な場合は、 この回答 (および質問)で問題を詳細に説明し、Shebang内で追加のスクリプトを使用して次のアプローチを提案します。

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,$_)
16
Anton

パス内のスペースやその他の特殊文字で失敗しないように、そして魔法についてもう少し説明するために、@ Antonの答えを拡張します。

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

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

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

この巧妙なスクリプトは、shとpythonの両方で理解できます。それぞれが異なる反応をします。 Shebangの後の最初の2行は、shによって解釈され、execを相対的なpythonバイナリ(同じコマンドライン引数で提供されます)に引き渡します。これらの同じ行は、=によって安全に破棄されます。 pythonは、文字列( "true")の後に複数行の文字列( '' ')が続くためです。

この主題についてもう少し読むことができます ここ

2
jeffre

python hashbang lineの移植可能な解決策を探しているときにこの質問を見つけ、上記の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)}

現在のディレクトリ内のすべてのスクリプトのハッシュバン行を変更するには、次のコマンドを実行できます。

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)}|' *
1

これらの回答を確認した後、実際にはShebangを変更しないPython固有のソリューションを使用することにしました。

このソリューションは、システムpythonインタープリターを使用して、目的のインタープリターを見つけて実行します。これにより、環境変数を変更したり、正しいインタープリターを確保したりできるため、便利です。

Execを使用するため、メモリ使用量が2倍になることはありません。新しいインタプリタが最後のインタプリタを置き換えます。また、終了ステータスとシグナルは正しく処理されます。

この環境で実行する必要があるのは、スクリプトによってロードされる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)の間のすべてを取り除くことができます。

0
Aaron Bentley