web-dev-qa-db-ja.com

スクリプトの開始後にインタープリターを選択します。 hashbang内のif / else

スクリプトを実行しているインタープリターを動的に選択する方法はありますか? 2つの異なるシステムで実行しているスクリプトがあり、使用したいインタープリターが2つのシステムの異なる場所にあります。私がしなければならないことは、私が切り替えるたびにハッシュバングの行を変更することです。 logicalに相当するものを実行したいと思います(この正確な構成は不可能であることを理解しています)。

if running on system A:
    #!/path/to/python/on/systemA
Elif running on system B:
    #!/path/on/systemB

#Rest of script goes here

または、これがより適切であり、最初のインタープリターを使用しようとし、見つからない場合は2番目のインタープリターを使用します。

try:
    #!/path/to/python/on/systemA
except: 
    #!path/on/systemB

#Rest of script goes here

明らかに、代わりにそれを/path/to/python/on/systemA myscript.pyまたは/path/on/systemB myscript.pyとして実行できますが、実際にはmyscript.pyを起動するラッパースクリプトがあるので、 python手作業ではなくプログラムでインタプリタ。

16
dkv

いいえ、動作しません。 2つの文字_#!_は、絶対にファイルの最初の2文字にする必要があります(とにかくifステートメントを解釈したものをどのように指定しますか?)。これは、関数のexec()ファミリが実行しようとしているファイルがスクリプト(インタープリターが必要)であるかバイナリファイル(そうではない)であるかを判別するときに検出する「マジックナンバー」を構成します。 )。

シバン行の形式は非常に厳密です。インタプリタへの絶対パスと最大で1つの引数が必要です。

canすることは、envを使用することです。

_#!/usr/bin/env interpreter
_

現在、envへのパスは通常 _/usr/bin/env_ですが、技術的には保証されていません。

これにより、各システムのPATH環境変数を調整して、interpreterbashpythonまたはPerlなど)あなたが持っている)が見つかりました。

このアプローチの欠点は、インタプリタに引数を移植可能に渡すことが不可能になることです。

この意味は

_#!/usr/bin/env awk -f
_

そして

_#!/usr/bin/env sed -f
_

一部のシステムでは機能しない可能性があります。

別の明らかなアプローチは、GNU autotools(またはいくつかの単純なテンプレートシステム)を使用してインタープリターを見つけ、正しいパスを_./configure_ステップでファイルに配置することです。各システムにスクリプトをインストールします。

明示的なインタープリターを使用してスクリプトを実行することもできますが、それは明らかに回避しようとしていることです。

_$ sed -f script.sed
_
27
Kusalananda

実際のプログラムの正しいインタープリターを見つけるために、いつでもラッパースクリプトを作成できます。

#!/bin/bash
if something ; then
    interpreter=this
    script=/some/path/to/program.real
    flags=()
else
    interpreter=that
    script=/other/path/to/program.real
    flags=(-x -y)
fi
exec "$interpreter" "${flags[@]}" "$script" "$@"

ユーザーのPATHにラッパーをprogramとして保存し、実際のプログラムを脇に置くか、別の名前を付けます。

flags配列のため、ハッシュバングで#!/bin/bashを使用しました。可変数のフラグなどを格納する必要がなく、それなしで実行できる場合、スクリプトは#!/bin/shと移植性よく機能するはずです。

27
ilkkachu

ポリグロットを作成することもできます(2つの言語を組み合わせる)。/bin/shの存在が保証されています。

これには醜いコードの欠点があり、おそらくいくつかの/bin/shsが混乱する可能性があります。ただし、envが存在しないか、/ usr/bin/env以外の場所に存在する場合に使用できます。かなり凝った選択をしたい場合にも使用できます。

スクリプトの最初の部分は、/ bin/shをインタープリターとして実行するときに使用するインタープリターを決定しますが、正しいインタープリターで実行すると無視されます。 execを使用して、シェルが最初の部分よりも多く実行されないようにします。

Pythonの例:

#!/bin/sh
'''
' 2>/dev/null
# Python thinks this is a string, docstring unfortunately.
# The Shell has just tried running the <newline> program.
find_best_python ()
{
    for candidate in pypy3 pypy python3 python; do
        if [ -n "$(which $candidate)" ]; then
            echo $candidate
            return
        fi
    done
    echo "Can't find any Python" >/dev/stderr
    exit 1
}
interpreter="$(find_best_python)"   # Replace with something fancier.
# Run the rest of the script
exec "$interpreter" "$0" "$@"
'''
11
Oskar Skog

これはシェルスクリプト内のインタープリターを選択しませんが(マシンごとに選択します)、スクリプトを実行しようとしているすべてのマシンへの管理アクセス権があれば、より簡単な代替手段です。

目的のインタープリターパスを指すシンボリックリンク(または必要に応じてハードリンク)を作成します。たとえば、私のシステムではPerlとpythonは/ usr/binにあります:

cd /bin
ln -s /usr/bin/Perl perl
ln -s /usr/bin/python python

/ bin/Perlなどでハッシュバングが解決できるようにするシンボリックリンクを作成します。これにより、スクリプトにパラメーターを渡す機能も保持されます。

2
Rob

私はKusalanandaとilkkachuの回答を好みますが、ここでは、質問が求められたからといって、質問が求めていることをより直接的に行う代替の回答を示します。

#!/usr/bin/Ruby -e exec "non-existing-interpreter", ARGV[0] rescue exec "python", ARGV[0]

if True:
  print("hello world!")

これは、インタープリターが最初の引数でコードの書き込みを許可している場合にのみ実行できることに注意してください。ここでは、-eおよびそれ以降のすべてが、Rubyの1つの引数として逐語的に解釈されます。 bash -cではコードを別の引数に含める必要があるため、私が知る限り、Shebangコードにbashを使用することはできません。

私はpythonで同じことを試みました:

#!/usr/bin/python -cexec("import sys,os\ntry: os.execlp('non-existing-interpreter', 'non-existing-interpreter', sys.argv[1])\nexcept: os.execlp('Ruby', 'Ruby', sys.argv[1])")

if true
  puts "hello world!"
end

しかし、それは長すぎることが判明し、linux(少なくとも私のマシンでは)はShebangを127文字に切り捨てます。 pythonは、try-exceptsまたは改行なしのexecsを許可しないため、importの使用を許しません。

これがどれほど移植性があるかはわかりませんし、配布を意図したコードでは実行しません。それにもかかわらず、それは実行可能です。多分誰かはそれが迅速で汚いデバッグまたは何かのために有用であることを見つけるでしょう。

2
JoL

私は今日、このような同様の問題に直面し(python3がpythonのバージョンを1つのシステムで古すぎるバージョンを指しています)、その方法とは少し異なるアプローチを考え出しましたここで説明したもの:「間違った」バージョンのpythonからbootstrapを「正しい」バージョンに使用します。制限はsome pythonのバージョンが確実に到達可能である必要があることですが、それは通常、たとえば#!/usr/bin/env python3

だから私がやっていることは私のスクリプトを開始することです:

#!/usr/bin/env python3
import sys
import os

# On one of our systems, python3 is pointing to python3.3
# which is too old for our purposes. 'Upgrade' if needed
if sys.version_info[1] < 4:
    for py_version in ['python3.7', 'python3.6', 'python3.5', 'python3.4']:
        try:
            os.execlp(py_version, py_version, *sys.argv)
        except:
            pass # Deliberately ignore errors, pick first available version

これは何ですか:

  • 通訳のバージョンを確認して、受け入れ基準を確認してください
  • 受け入れられない場合は、候補バージョンのリストに目を通し、使用可能なバージョンの最初のものを使用して自分自身を再実行します。
0
microtherion