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


15

さまざまな環境(およびPATH)を持ち、さまざまなLinuxシステムでさまざまなユーザーが理論的に実行できるPythonスクリプトがあるという興味深い状況に少しあります。このスクリプトは、人為的な制限なしにこれらの可能な限り多くで実行できるようにします。既知のセットアップを次に示します。

  • 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にフォールバックしてから、それらのどれも存在しなかった場合、単にエラーになります。ただし、可能な場合は正しいインタープリターの1つを見つけることができる限り、可能な限り最新の2.xを使用してハングアップしません。

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

#!/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スクリプトを実行する別のシェルスクリプトを作成することもできます。最新のpython 2インタープリターをインストールして実行する限り、1つで十分な2つのファイルを配布するのは面倒です。インタプリタを明示的に呼び出す(たとえば、$ python2.5 script.py)ように人々に依頼することは選択肢ではありません。特定の方法で設定されているユーザーのPATHに依存することもオプションではありません。

編集:

Python 2.6の時点で存在する(with with 2.5で使用できる)"with"ステートメントを使用しているため、Pythonスクリプト内のバージョンチェックは機能しませんfrom __future__ import with_statement。これにより、スクリプトはユーザーフレンドリーでないSyntaxErrorですぐに失敗し、最初にバージョンをチェックして適切なエラーを出力する機会を得ることができなくなります。

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

#!/usr/bin/env python

import sys

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

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

本当にあなたが望むものではありませんので、コメントしてください。ただしimport sys; sys.version_info()、ユーザーが必要なPythonバージョンを持っているかどうかを確認するために使用できます。
ベルンハルト

2
@Bernhardはい、これは事実ですが、それまでに何もするのは遅すぎます。上記のスクリプトを直接実行する(つまり./script.py)3番目の状況では、python2.4がスクリプトを実行し、コードが間違ったバージョンであることを検出します(おそらく終了します)。しかし、代わりにインタープリターとして使用できた、完全に優れたpython2.5があります!
user108471

2
ラッパースクリプトを使用して、適切なpythonが存在するかどうかを確認し、存在する場合execはエラーを出力します。
ケビン

1
したがって、同じファイルで最初に実行してください。
ケビン

9
@ user108471:shebang行はbashによって処理されると仮定しています。そうではなく、システムコール(execve)です。引数は文字列リテラルであり、グロビングも正規表現もありません。それでおしまい。最初の引数が「/ bin / bash」で、2番目のオプション(「-c ...」)であっても、これらのオプションはシェルによって解析されません。これらはbash実行可能ファイルに未処理のまま渡されるため、これらのエラーが発生します。さらに、シバンは最初の段階でのみ機能します。あなたはここで運が悪いので、私は怖いです(pythonインタープリターを見つけて、それをここでdocにフィードするスクリプトが不足しています)。
goldilocks

回答:


12

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

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

#!/usr/bin/env python

または

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

それはされ、推奨 Pythonの ドキュメントのすべてのバージョンでは:

通常、良い選択は

#!/usr/bin/env python

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

さまざまなディストリビューションでは、Pythonはさまざまな場所にインストールされる可能性があるenvため、で検索しPATHます。すべての主要なLinuxディストリビューションと、FreeBSDで見られるものから入手できるはずです。

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

スクリプトが2.4を除くすべてのバージョンのPythonと互換性がある場合、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

2
私がやりたいことは、グッドプラクティスと見なされることに反することです。あなたが投稿したものは完全に合理的ですが、同時にそれは私が求めているものではありません。気になるすべての状況でPythonの適切なバージョンを完全に検出できる場合、ユーザーがPythonの適切なバージョンをスクリプトで明示的に指定する必要はありません。
user108471

1
「Python 2.4で実行され、情報を出力して終了する場合は、内部をチェックするだけではいけない」理由については、私の更新を参照してください。
user108471

あなたが正しいです。SOでこの質問を見つけたので、ファイルを1つだけにしたい場合、それを行うオプションがないことがわかりました
...-pbm

9

いくつかのコメントからのいくつかのアイデアに基づいて、私はうまくいっているように見える本当にtrulyいハックを一緒に丸ごとまとめることができました。スクリプトはbashスクリプトになり、Pythonスクリプトをラップし、「hereドキュメント」を介して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

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

いくつかのシェナンガンに関する説明:

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

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

これはひねくれているように感じるので、誰かがより良い解決策を持っていることを願っています。


たぶんそれはそれほど厄介ではない;)+1
goldilocks

これの欠点は、構文アップ混乱はほとんどのエディタ上で着色することである
リーライアン

@LieRyanそれは依存します。私のスクリプトは.pyファイル名拡張子を使用します。ほとんどのテキストエディターは、色付けに使用する構文を選択するときにこの拡張子を好みます。仮に、.py拡張子を付けずにこれの名前を変更する場合、次のようなものを使用して、モードラインを使用して(少なくともVimユーザーに対して)正しい構文を示唆することができます# ft=python
user108471

7

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

最も明らかな解決策は、ラッパースクリプトを記述することです。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つのファイルに入れたい場合は、行で始まる(シェルによって実行される)ポリグロットにすることができますが#!/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番目の解決策は# EOFこの回答のように最後に追加する必要がないため便利です。あなたのアプローチは基本的にここで概説されたものと同じです
sschuberth

6

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

バイナリでタグ付けされたバージョンが実行中の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")

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


execv'ing後も実行を継続しませんか?だから、通常のものは2回実行されますか?
ヤヌストロエルセン

1
execvは、現在実行中のプログラムを新しくロードされたプログラムイメージで置き換えます
Matt

これは、私が思いついたものよりもwayく感じない素晴らしいソリューションのように見えます。これがこの目的で機能するかどうかを確認する必要があります。
user108471

この提案は、私が必要とするものに対してほとんど機能します。唯一の欠陥は、私が最初に言及しなかったものです。Python2.5をサポートするためfrom __future__ import with_statementに、Pythonスクリプトの最初のものでなければならないを使用します。新しいインタープリターを起動するときに、そのアクションを実行する方法を知っているとは思わないでしょうか?
user108471

あなたはそれがあることが必要であることを確認されている非常に最初の事?またはwith、通常のインポートのようなs を使用しようとする直前に?if mypy == 25: from __future__ import with_statement「通常のもの」の直前に余分な作業があり ますか?2.4をサポートしていない場合、おそらくifは必要ありません。
マット

0

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

#!/my/python/search/script

そして、このスクリプトは単純に(検索後):

"$python_path" "$1"

カーネルがこのスクリプトインダイレクションを受け入れるかどうかはわかりませんでしたが、チェックして動作しました。

編集1

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

1つのファイルに両方のスクリプトを組み合わせることができます。pythonスクリプトをhereドキュメントとして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

これは多かれ少なかれ、質問の最後の段落ですでに述べられた解決策です!
ベルンハルト

賢いですが、すべてのシステムのどこかにこのマジックスクリプトをインストールする必要があります。
user108471

@Bernhard Oooops、キャッチ。将来的には最後まで読みます。補償として、1ファイルソリューションに改善します。
ハウケレイジング

@ user108471マジックスクリプトには次のようなものを含めることができます。$(ls /usr/bin/python?.? | tail -n1 )しかし、私はこれをシバンで巧みに使用することに成功しませんでした。
ベルンハルト

@Bernhardあなたはシバンライン内で検索をしたいですか?IIRCカーネルは、シバン行の引用を気にしません。そうでなければ(これが変更された場合)、 `#!/ bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "のようにできます。
ハウケレイジング
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.