シバングの複数の引数


32

shebang行(#!)を介して実行可能ファイルに複数のオプションを渡す一般的な方法があるかどうか疑問に思っています。

私はNixOSを使用していますが、私が書くスクリプトの最初の部分は通常/usr/bin/envです。私が遭遇する問題は、その後に来るすべてがシステムによって単一のファイルまたはディレクトリとして解釈されることです。

たとえば、bashposixモードで実行されるスクリプトを書きたいとします。シバンを書く簡単な方法は次のとおりです。

#!/usr/bin/env bash --posix

ただし、結果のスクリプトを実行しようとすると、次のエラーが生成されます。

/usr/bin/env: ‘bash --posix’: No such file or directory

私はこの投稿を知っていますが、より一般的でクリーンなソリューションがあるかどうか疑問に思っていました。


編集Guileスクリプトには、マニュアルのセクション4.3.4に記載されている、目的を達成する方法があることを知っています。

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

ここでのトリックは、2番目の行(で始まるexec)がコードとして解釈されますshが、#!... !#ブロック内にあるため、コメントとして解釈されるため、Guileインタープリターによって無視されることです。

このメソッドをインタープリターに一般化することはできませんか?


2番目の編集:少し遊んだ後、から入力を読み取ることができるインタープリターのstdin場合、次のメソッドが機能するようです:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

ただし、sh通訳が仕事を終えるまでプロセスは存続するため、おそらく最適ではありません。フィードバックや提案をいただければ幸いです。



回答:


27

Linuxカーネルはshebang行の最初の「単語」に続くすべてを単一の引数として扱うため少なくともLinuxをサポートする必要がある場合は一般的な解決策はありません。

NixOSの制約が何であるかはわかりませんが、通常は次のように書きます。

#!/bin/bash --posix

または、可能であれば、スクリプトでオプションを設定します

set -o posix

または、適切なシェル呼び出しでスクリプトを再起動することもできます。

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

このアプローチは、最初の数行(シェルによって解釈される)がターゲット言語によって無視される方法を見つける限り、他の言語に一般化できます。

GNU coreutils' envは、バージョン8.30以降の回避策を提供します。詳細については、uノード回答を参照してください。(これはDebian 10以降、RHEL 8以降、Ubuntu 19.04以降などで利用可能です。)


18

厳密には移植性はありませんが、coreutils 8.30から開始し、そのドキュメントに従って、次を使用できます。

#!/usr/bin/env -S command arg1 arg2 ...

与えられた:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

あなたが取得します:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

あなたが好奇心が強い場合showargsは:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

これは、今後の参考のために知っておくと非常に便利です。
ジョンマクゲイヒー

そのオプションは、FreeBSDのからコピーされた2005年を参照の中に追加されましたlists.gnu.org/r/coreutils/2018-04/msg00011.htmlenv-S
ステファンChazelas

Fedora 29
Eric

いくつかの改良@unode showargspastebin.com/q9m6xr8Hpastebin.com/gS8AQ5WA(ワンライナー)
エリック・

FYI:8.31のcoreutilsのように、env独自のを含んでいるshowargs-vオプションの#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

POSIX標準は、次のように記述するのが非常に簡潔#!です。

システムインターフェースファミリのドキュメントの理論的セクションexec()から:

一部の歴史的な実装がシェルスクリプトを処理する別の方法は、ファイルの最初の2バイトを文字列として認識し、#!実行するコマンドインタープリターの名前としてファイルの最初の行の残りを使用することです。

シェルはじめセクション

シェルは、ファイル(「参考文献」を参照sh)、-cオプション、またはPOSIX.1-2008のSystem Interfacesボリュームで定義されたsystem()and popen()関数から入力を読み取ります。シェルコマンドのファイルの最初の行が文字#!で始まる場合、結果は不定です。

これは基本的に、どの実装(使用しているUnix)も、必要に応じてシバン行の解析の詳細を自由に実行できることを意味します。

macOS(ATMをテストできない)のような一部のUnicesは、シバン行でインタープリターに渡された引数を個別の引数に分割しますが、Linuxおよび他のほとんどのUnicesは、インタープリターに単一のオプションとして引数を渡します。

したがって、複数の引数を取ることができるシバンラインに依存することは賢明ではありません。

ウィキペディアのShebang記事の移植性セクションも参照してください。


任意のユーティリティまたは言語に一般化できる簡単なソリューションの1つは、適切なコマンドライン引数を使用して実際のスクリプトを実行するラッパースクリプトを作成することです。

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

私はそれがややもろいように感じるので、自分で再実行させようとは思わないでしょう。


7

シバンについては、execve(2) manページで次のように説明されています。

#! interpreter [optional-arg]

この構文では、2つのスペースを使用できます。

  1. インタープリターパスの前に1つのスペースがありますが、このスペースはオプションです。
  2. インタプリタパスとそのオプションの引数を区切る1つのスペース。

オプションの引数について話すときには複数形を使用しなかったことに注意してください。多くの場合[optional-arg ...]単一の引数を指定できるため、上記の構文も使用しません

シェルスクリプトに関する限り、スクリプトsetの先頭近くで組み込みコマンドを使用すると、インタープリターパラメーターを設定でき、コマンドライン引数を使用した場合と同じ結果が得られます。

あなたの場合:

set -o posix

Bashプロンプトから、の出力を確認して、help set使用可能なすべてのオプションを取得します。


1
3つ以上のスペースを使用できますが、それらはオプションの引数の一部と見なされます。
スティーブンキット

@StephenKitt:実際、ここの空白は、実際のスペース文字よりもカテゴリーとしてとらえるべきです。タブなどの他の空白も広く受け入れられると思います。
WhiteWinterWolf

3

Linuxでは、シバンはあまり柔軟ではありません。複数の回答(Stephen Kittの回答およびJörgW Mittag の回答)によると、シバン行で複数の引数を渡す指定された方法はありません。

誰に役立つかはわかりませんが、不足している機能を実装するための短いスクリプトを作成しました。https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efaを参照してください

埋め込まれた回避策を記述することもできます。以下に、同じテストスクリプトに適用される4つの言語に依存しない回避策と、それぞれが出力する結果を示します。スクリプトは実行可能で、にあると思います/tmp/shebang


プロセス置換内のbash heredocでのスクリプトのラップ

私の知る限り、これは最も信頼できる言語に依存しない方法です。引数を渡すことができ、標準入力を保持します。欠点は、インタープリターが読み取るファイルの(実際の)場所を知らないことです。

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'プリントの呼び出し:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

プロセス置換により特別なファイルが生成されることに注意してください。これは、すべての実行可能ファイルに適合するとは限りません。たとえば、#!/usr/bin/less文句を言う:/dev/fd/63 is not a regular file (use -f to see it)

ダッシュのプロセス置換内にヒアドキュメントを含めることが可能かどうかはわかりません。


単純なヒアドキュメントでスクリプトをラップする

短くてシンプルですがstdin、スクリプトからアクセスできず、インタープリターがからスクリプトを読み取って実行できる必要がありますstdin

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'プリントの呼び出し:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

system()引数なしでawk 呼び出しを使用する

実行されたファイルの名前を正しく渡しますが、スクリプトは指定した引数を受け取りません。awkは私が知っている唯一の言語であり、どちらのインタプリタもデフォルトでLinuxにインストールされ、デフォルトでコマンドラインからその命令を読み取ることに注意してください。

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'プリントの呼び出し:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

system()引数にスペースが含まれていない場合は、awk 4.1+ 呼び出しを使用します

すてきですが、スペースを含む引数でスクリプトが呼び出されないことが確実な場合のみです。ご覧のとおり、スペースをエスケープしない限り、スペースを含む引数は分割されます。

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'プリントの呼び出し:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

4.1より前のawkバージョンでは、forループ内で文字列連結を使用する必要があります。関数の例https://www.gnu.org/software/gawk/manual/html_node/Join-Function.htmlを参照してください


1
禁止にここに文書ターミネータを引用$variableまたは`command`置換:exec python3 -O <(cat <<'EOWRAPPER'
ジョン・マクギー

2

(シェバン)行LD_LIBRARY_PATHでpython を使用するコツ#!は、シェル以外には依存せず、おやつを機能させます。

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

このページの他の場所で説明されているように、一部のシェルshは標準入力でスクリプトを使用できます。

提供するスクリプトは(空の文字列)に簡略化されshたコマンドを実行しようとしますが、もちろんコマンドがないため実行に失敗します。したがって、通常は標準エラー記述子に出力しますが、表示するのが面倒でユーザーを混乱させるため、最も近いブラックホール。''''''sh''line 2: command not found2>/dev/nullsh

次に、目的のコマンドに進みexecます。現在のシェルプロセスを次のように置き換え/usr/bin/env pythonます。この場合、適切なパラメーターを使用します。

  • "$0" どのスクリプトを開いて解釈する必要があるかをPythonに通知し、設定する sys.argv[0]
  • "$@"sys.argv[1:]スクリプトコマンドラインで渡された引数にpythonを設定します。

また、ハッキングの唯一のポイントである環境変数envを設定することも求めていLD_LIBRARY_PATHます。

シェルコマンド#は、末尾の三重引用符を無視するように、コメントで終了します'''

sh次に、最初の引数("$0")として指定されたpythonソーススクリプトを開いて読み取るPythonインタープリターの新しいインスタンスに置き換えられます。

Pythonはファイルを開き、-x引数のおかげでソースの1行目をスキップします。注:-xPythonではシェバンは単なるコメントであるため、これも機能しません

Pythonは2行目を現在のモジュールファイルのdocstringとして解釈します。したがって、有効なモジュールdocstringが必要な場合__doc__は、上記の例のようにpythonプログラムで最初に設定するだけです。



空の文字列が…ええと…空だとすれば、あなたはモンキービジネスが見つからないコマンドを落とすことができる''''exec ...はずです:仕事を終わらせるべきです。execの前にスペースがないことに注意してください。空白がないと、空のコマンドが検索されます。あなたは最初の引数に空をスプライスしたいのでそう$0ですexec
カレブ

1

単一の引数としてスクリプトを除く実行可能ファイルを探しているときに、かなり愚かな回避策を見つけました。

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.