スクリプトを実行しているインタープリターを動的に選択する方法はありますか? 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手作業ではなくプログラムでインタプリタ。
いいえ、動作しません。 2つの文字_#!
_は、絶対にファイルの最初の2文字にする必要があります(とにかくifステートメントを解釈したものをどのように指定しますか?)。これは、関数のexec()
ファミリが実行しようとしているファイルがスクリプト(インタープリターが必要)であるかバイナリファイル(そうではない)であるかを判別するときに検出する「マジックナンバー」を構成します。 )。
シバン行の形式は非常に厳密です。インタプリタへの絶対パスと最大で1つの引数が必要です。
canすることは、env
を使用することです。
_#!/usr/bin/env interpreter
_
現在、env
へのパスは通常 _/usr/bin/env
_ですが、技術的には保証されていません。
これにより、各システムのPATH
環境変数を調整して、interpreter
(bash
、python
またはPerl
など)あなたが持っている)が見つかりました。
このアプローチの欠点は、インタプリタに引数を移植可能に渡すことが不可能になることです。
この意味は
_#!/usr/bin/env awk -f
_
そして
_#!/usr/bin/env sed -f
_
一部のシステムでは機能しない可能性があります。
別の明らかなアプローチは、GNU autotools(またはいくつかの単純なテンプレートシステム)を使用してインタープリターを見つけ、正しいパスを_./configure
_ステップでファイルに配置することです。各システムにスクリプトをインストールします。
明示的なインタープリターを使用してスクリプトを実行することもできますが、それは明らかに回避しようとしていることです。
_$ sed -f script.sed
_
実際のプログラムの正しいインタープリターを見つけるために、いつでもラッパースクリプトを作成できます。
#!/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
と移植性よく機能するはずです。
ポリグロットを作成することもできます(2つの言語を組み合わせる)。/bin/shの存在が保証されています。
これには醜いコードの欠点があり、おそらくいくつかの/bin/sh
sが混乱する可能性があります。ただし、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" "$@"
'''
これはシェルスクリプト内のインタープリターを選択しませんが(マシンごとに選択します)、スクリプトを実行しようとしているすべてのマシンへの管理アクセス権があれば、より簡単な代替手段です。
目的のインタープリターパスを指すシンボリックリンク(または必要に応じてハードリンク)を作成します。たとえば、私のシステムではPerlとpythonは/ usr/binにあります:
cd /bin
ln -s /usr/bin/Perl perl
ln -s /usr/bin/python python
/ bin/Perlなどでハッシュバングが解決できるようにするシンボリックリンクを作成します。これにより、スクリプトにパラメーターを渡す機能も保持されます。
私は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または改行なしのexec
sを許可しないため、import
の使用を許しません。
これがどれほど移植性があるかはわかりませんし、配布を意図したコードでは実行しません。それにもかかわらず、それは実行可能です。多分誰かはそれが迅速で汚いデバッグまたは何かのために有用であることを見つけるでしょう。
私は今日、このような同様の問題に直面し(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
これは何ですか: