web-dev-qa-db-ja.com

スクリプトのシバン行に複数の可能性を持たせるにはどうすればよいですか?

Pythonスクリプトは理論的にはさまざまな環境(およびPATH)を備えたさまざまなユーザーがさまざまなLinuxで実行できる)があるちょっと興味深い状況にいますシステムこのスクリプトは、人為的な制限なしに、可能な限り多くのシステムで実行できるようにしたいと考えています。既知の設定をいくつか次に示します。

  • Python 2.6はシステムですPythonバージョンなので、python、python2、python2.6はすべて/ usr/binに存在します(同等です)。
  • Python 2.6はシステムですPythonバージョン、上記と同じですが、Python 2.7がpython2.7としてインストールされます。
  • Python 2.4はシステムですPythonバージョン、私のスクリプトはサポートしていません。/usr/binには、同等のpython、python2、およびpython2.4、およびpython2.5があります。スクリプトがサポートします。

これらの3つすべてで同じ実行可能ファイルpythonスクリプトを実行します。最初に/usr/bin/python2.7が存在する場合は、それを使用してからフォールバックするといいでしょう。 /usr/bin/python2.6に移動し、次に/usr/bin/python2.5にフォールバックし、それらが存在しない場合は単にエラーアウトします。最新の2.xを使用して、あまりハングアップしていませんただし、存在する場合に正しいインタープリターの1つを見つけることができる限り可能です。

私の最初の傾向は、シバン線を次のように変更することでした。

#!/usr/bin/python

#!/usr/bin/python2.[5-7]

これはbashでは正常に機能するためです。しかし、スクリプトを実行すると、次のようになります。

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory

さて、私は次のことを試します、これはbashでも機能します:

#!/bin/bash -c /usr/bin/python2.[5-7]

しかし、これも失敗します:

/bin/bash: - : invalid option

さて、明らかに、正しいインタープリターを見つけて、見つけたインタープリターを使用してpythonスクリプトを実行する別のシェルスクリプトを書くことができます。1つの場所に2つのファイルを配布するのは面倒だと思います。最新のpython 2インタプリタがインストールされている状態で実行されている限り、これで十分です。インタプリタを明示的に呼び出すように人々に依頼すること(例:$ python2.5 script.py)はオプションではありません。特定の方法で設定されているユーザーのPATHに依存することもオプションではありません。

編集:

Pythonの時点で存在する「with」ステートメントを使用しているため、Pythonスクリプトはnot内でバージョンチェックが機能します。 = 2.6(およびfrom __future__ import with_statementを使用して2.5で使用できます)これにより、スクリプトはすぐに失敗し、ユーザーに不便なSyntaxErrorが発生し、最初にバージョンを確認して適切なエラーを出力する機会がなくなります。

例:(Pythonインタープリターが2.6未満でこれを試してください)

#!/usr/bin/env python

import sys

print "You'll never see this!"
sys.exit()

with open('/dev/null', 'w') as out:
    out.write('something')
16
user108471

いくつかのコメントからのいくつかのアイデアに基づいて、私はうまくいくと思われる本当に醜いハックを一緒にまとめることができました。スクリプトはbashスクリプトになり、Pythonスクリプトをラップして、「ヒアドキュメント」を介してPythonインタプリタに渡します。

最初に:

#!/bin/bash

''':'
vers=( /usr/bin/python2.[5-7] )
latest="${vers[$((${#vers[@]} - 1))]}"
if !(ls $latest &>/dev/null); then
    echo "ERROR: Python versions < 2.5 not supported"
    exit 1
fi
cat <<'# EOF' | exec $latest - "$@"
''' #'''

Pythonコードがここに入ります。最後に、

# EOF

ユーザーがスクリプトを実行すると、最新のPythonバージョン2.5〜2.7が使用され、スクリプトの残りの部分がヒアドキュメントとして解釈されます。

いくつかの悪魔の説明:

追加した三重引用符を使用すると、この同じスクリプトをPythonモジュール(テスト目的で使用)としてインポートできます。Pythonでインポートすると、最初と2番目の間のすべてがtriple-single-quoteはモジュールレベルの文字列として解釈され、3番目のtriple-single-quoteはコメント化されます。残りは通常のPythonです。

(bashスクリプトとして)直接実行すると、最初の2つの単一引用符は空の文字列になり、3番目の単一引用符はコロンだけを含む4番目の単一引用符とともに別の文字列を形成します。この文字列はBashによって何も実行されないものとして解釈されます。その他すべては、Pythonバイナリを/ usr/binに展開し、最後のバイナリを選択し、execを実行して、残りのファイルをヒアドキュメントとして渡すためのBash構文です。ヒアドキュメントは、 a Pythonハッシュ/ポンド/オクトソープ記号のみを含むトリプルシングルクォート。スクリプトの残りの部分は、「#EOF」と表示されている行がhereドキュメントを終了するまで通常どおり解釈されます。

これはひどいことだと思うので、誰かがより良い解決策を持っていることを願っています。

9
user108471

私は専門家ではありませんが、使用する正確なpythonバージョンを指定しないでください。システム/ユーザーにその選択を任せてください。

また、スクリプトでpythonへのパスをハードコーディングする代わりにそれを使用する必要があります。

#!/usr/bin/env python

または

#!/usr/bin/env python3 (or python2)

すべてのバージョンで 推奨byPythondoc です:

通常は良い選択です

#!/usr/bin/env python

これは、PATH全体でPython=インタプリタを検索します。ただし、一部のUnicesにはenvコマンドがない場合があるため、/ usr/bin/pythonをインタプリタパスとしてハードコーディングする必要があります。

さまざまなディストリビューションではPythonはさまざまな場所にインストールされる可能性があるため、envPATHで検索します。すべての主要なLinuxディストリビューションおよび私がFreeBSDで目にするもの。

スクリプトは、そのバージョンのPython PATHにあり、ディストリビューションによって選択されます*)で実行する必要があります。

スクリプトがPython 2.4以外のすべてのバージョンと互換性がある場合は、スクリプトがPython 2.4で実行されているかどうかを確認し、情報を出力して終了する必要があります。 。

もっと読む

  • ここ さまざまなシステムにインストールされている場所Pythonの例を見つけることができます。
  • ここenvを使用することの利点と欠点を見つけることができます。
  • ここ PATH操作と異なる結果の例を見つけることができます。

脚注

* Gentooにはeselectというツールがあります。これを使用すると、さまざまなアプリケーション(Pythonを含む)のデフォルトバージョンをデフォルトとして設定できます。

$ eselect python list
Available Python interpreters:
  [1]   python2.6
  [2]   python2.7 *
  [3]   python3.2
$ Sudo eselect python set 1
$ eselect python list
Available Python interpreters:
  [1]   python2.6 *
  [2]   python2.7
  [3]   python3.2
13
pbm

シバン行は、インタープリターへの固定パスのみを指定できます。 PATHでインタープリターを検索するための#!/usr/bin/envトリックがありますが、それだけです。さらに高度な機能が必要な場合は、ラッパーシェルコードを記述する必要があります。

最も明白な解決策は、ラッパースクリプトを記述することです。 pythonスクリプトfoo.realを呼び出して、ラッパースクリプトfooを作成します。

#!/bin/sh
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi

すべてを1つのファイルに入れたい場合、それを polyglot にして、#!/bin/sh行で始まるようにすることができます(シェルによって実行されます)が、他の言語。言語によっては、ポリグロットが不可能な場合があります(たとえば、#!によって構文エラーが発生した場合)。 Pythonでは、それほど難しくありません。

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi
'''
# real Python script starts here
def …

''''''の間のテキスト全体は、トップレベルのPython文字列であり、効果はありません。シェルの場合、2行目は''':'です引用符を取り除いた後は、no-opコマンド:になります。)

要件には既知のバイナリのリストが記載されているため、Pythonで次のように実行できます。1桁のマイナー/メジャーバージョンのPythonでも、すぐにそうなるとは思いません。

バイナリにタグ付けされたバージョンがpython実行中の現在のバージョンよりも高い場合、バージョン付きpythonの順序付きの増加リストからディスク上にある最も高いバージョンを実行します。「バージョンの順序付き増加リスト"このコードの重要なビットです。

#!/usr/bin/env python
import os, sys

pythons = [ '/usr/bin/python2.3','/usr/bin/python2.4', '/usr/bin/python2.5', '/usr/bin/python2.6', '/usr/bin/python2.7' ]
py = list(filter( os.path.isfile, pythons ))
if py:
  py = py.pop()
  thepy = int( py[-3:-2] + py[-1:] )
  mypy  = int( ''.join( map(str, sys.version_info[0:2]) ) )
  if thepy > mypy:
    print("moving versions to "+py)
    args = sys.argv
    args.insert( 0, sys.argv[0] )
    os.execv( py, args )

print("do normal stuff")

私のスクラッチパイソンの謝罪

6
Matt

使用可能なphython実行可能ファイルをチェックし、スクリプトをパラメーターとして呼び出して、小さなbashスクリプトを作成できます。次に、このスクリプトをシバンラインターゲットにすることができます。

#!/my/python/search/script

そして、このスクリプトは単に(検索後に)行います。

"$python_path" "$1"

カーネルがこのスクリプトの間接参照を受け入れるかどうかはわかりませんでしたが、チェックして機能しました。

編集1

この恥ずかしい非知覚を最終的に良い提案にするために:

両方のスクリプトを1つのファイルに結合することが可能です。 pythonスクリプトをヒアドキュメントとしてbashスクリプトに記述します(pythonスクリプトを変更する場合は、スクリプトを再度一緒にコピーする必要があるだけです)。)たとえば/ tmpに一時ファイルを作成するか、(pythonがサポートしている場合はわかりません)スクリプトをインタープリターへの入力として提供します。

# do the search here and then
# either
cat >"tmpfile" <<"EOF" # quoting EOF is important so that bash leaves the python code alone
# here is the python script
EOF
"$python_path" "tmpfile"
# or
"$python_path" <<"EOF"
# here is the python script
EOF
0
Hauke Laging