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


16

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

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インタープリターへのパスを指定したいと思います。


3
「スクリプトの残り」をシバンなしでインタープリターにファイルとして渡し、if条件を使用することはオプションではありませんか?のようにif something; then /bin/sh restofscript.sh elif...
マズ

それはオプションです、私もそれを考えました、しかし、私が望むよりいくらか厄介です。hashbang行のロジックは不可能なので、私は実際にその道を行くと思います。
dkv

この質問が生み出したさまざまな答えが気に入っています。
オスカースコグ

回答:


27

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

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

あなたができることは使用することですenv

#!/usr/bin/env interpreter

現在、へのパスenv通常 /usr/bin/envですが、技術的にはそれは保証ではありません。

これによりPATH、各システムの環境変数を調整して、interpreter(it bashpythonまたはperlあなたが持っているもの)が見つかるようにすることができます。

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

この意味は

#!/usr/bin/env awk -f

そして

#!/usr/bin/env sed -f

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

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

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

$ sed -f script.sed

#!そうです、その行を処理するのはシェルではないので、それが最初に来る必要があることを理解しています。if / elseと同等のロジック hashbang行内に配置する方法があるかどうか疑問に思っていました。私はまた、私との干渉を避けたいと思ってPATHいましたが、それが私の唯一の選択肢だと思います。
dkv

1
を使用#!/usr/bin/awkする場合、として1つの引数のみを指定できます#!/usr/bin/awk -f。指しているバイナリがの場合、env引数はのenvように、探しているバイナリです#!/usr/bin/env awk
DopeGhoti

2
@dkvそうではありません。2つの引数を持つインタープリターを使用し、一部のシステムでは動作しますが、すべてではありません。
クサラナナンダ

3
Linuxでは@dkv /usr/bin/envが単一の引数で実行されますawk -f
イルッカチュ

1
@Kusalananda、いや、それがポイントでした。foo.awkhashbang行#!/usr/bin/env awk -fで呼び出されるスクリプトがあり、それで呼び出す場合、./foo.awkLinuxではenv、2つのパラメーターawk -fとが表示され./foo.awkます。実際には/usr/bin/awk -f、スペースのある(など)を探します。
イルッカチュ

27

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

#!/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、脇や別の名前で、実際のプログラムを置きます。

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


2
私が見てきたexec "$interpreter" "${flags[@]}" "$script" "$@"プロセスツリークリーナーを維持するために使用されるも。また、終了コードを伝播します。
ラウエンツァ

@rrauenza、そうそう、当然のことながらexec
-ilkkachu

1
#!/bin/sh代わりに良くならないでしょう #!/bin/bashか?/bin/sh別のシェルへのシンボリックリンクであっても、ほとんどの(すべてではないにしても)* nixシステムに存在する必要があります。さらに、スクリプトの作成者はbashismに陥るのではなく、移植可能なスクリプトを作成する必要があります。
セルギーコロディアズニー

@SergiyKolodyazhnyy、ねえ、私はそれを以前に言及することを考えたが、そうではなかった。使用される配列flagsは非標準の機能ですが、可変数のフラグを保存するのに十分役立つので、それを保持することにしました。
イルッカチュ

または、/ bin / shを使用して、各ブランチでインタープリターを直接呼び出しますscript=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@"。execエラーのエラーチェックを追加することもできます。
jrw32982は

11

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

これにはいコードの欠点があり、おそらくいくつか/bin/shは混乱する可能性があります。ただし、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" "$@"
'''

3
あなたはおそらくしたい、私は私が前に、これらのいずれかを見てきたと思いますが、アイデアはまだ同じようにひどいです...しかしexec "$interpreter" "$0" "$@"、あまりにも、実際のインタプリタにスクリプト自体の名前を取得します。(そして、セットアップ時に誰も嘘をつかないことを願ってい$0ます。)
ilkkachu

6
実際#!、Scalaは構文でポリグロットスクリプトをサポートしています。Scalaスクリプトがで始まる場合、Scala は一致するものまですべてを無視します!#。これにより、任意の複雑なスクリプトコードを任意の言語でそこに配置しexec、Scala実行エンジンでスクリプトを使用できます。
ヨルグWミットタグ

1
@
JörgW

2

私はクサラナンダとイルカチュウの答えが好きですが、ここでは、質問が求められたという理由だけで、質問が求めていたことをより直接的に行う代替答えがあります。

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

if True:
  print("hello world!")

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

私はシェバンコードのために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(少なくとも私のマシンでは)はシェバンを127文字に切り捨てます。execPythonではimport改行なしのtry-exceptsまたはsが許可されていないため、改行を挿入するのはご容赦ください。

これがどれほど移植性があるかはわかりませんし、配布するコードに対してはそうしません。それにもかかわらず、それは実行可能です。たぶん誰かがそれを手っ取り早いデバッグや何かのために便利だと思うでしょう。


2

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

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

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

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


1
+1これはとても簡単です。お気づきのように、この質問にはまったく答えていませんが、OPが望んでいることを正確に行えるようです。envを使用すると、マシンの問題ごとにルートアクセスが回避されます。
ジョー

0

今日、このような類似の問題に直面しました(python31つのシステムでは古すぎるpythonのバージョンを指しています)。ここで説明したものとは少し異なるアプローチを思い付きました。 「正しい」ものにブートストラップするPython。制限は、あるバージョンの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

これがすることは:

  • いくつかの受け入れ基準については、インタープリターのバージョンを確認してください
  • 受け入れられない場合は、候補バージョンのリストを確認し、利用可能な最初のバージョンで再実行します
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.