この質問のバリエーションが何度も要求されました(ここそしてここ、例えば)しかし、答えが私の質問を完全に捉えていないか、私が知っているよりも多くを想定するのは心配です。
たとえば、質問をしますが、おおよそ私が理解しようとしているのは(1)どのようにシェルは実行可能ファイルとスクリプトを認識できますが、もしそうなら(2)認識後に次に何が起こるのか?
私の作業ディレクトリにシェルスクリプトと実行可能ファイルがあるとしますscript
(「実行可能」はバイナリ「マシンコード」を意味すると理解していますが、正確ではないかもしれません)exe
。 bashシェルと対話していて、script
bashとtcshの両方がこれを実行できるとしましょう。さらに、最初の行を仮定すると確かにShebangで始めましょう#!...
。
script
コマンドラインプロンプトに入力するとします。 Bashはこれが実行可能ファイルではなくスクリプトであるかどうかを判断し、判断したら何をしますか? (私の考えに答えると、「新しいプロセス、特に新しいシェル(サブシェルなど)を作成し、この場合bash(shebangがないため)を作成し、その中にあるスクリプトからコマンドを実行することです」と思います。わからない。)exe
それでは、コマンドラインプロンプトに入力したとします。 Bashはこれがスクリプトではなく実行可能ファイルであるかどうかを判断しますか?一度決定したら何をしますか? (私の考えでは、「新しいプロセスを作成して実行可能ファイルが完了するのを待つ」という答えが出てくるようですが、わかりません。)それでは、最初の行に
script
含めるように変更したとしましょう。#! /bin/tcsh
bashはこれがスクリプト((1)への答えでshebangが何を変更しますか?)であり、実行可能ファイルではないことをどのように決定し、決定したら何をしますか? (私の考えに答えると、「新しいプロセス、特に新しいシェル(つまりサブシェル)を作成し、この場合はtcsh(私はshebangだから)を作成し、その中のスクリプトでコマンドを実行することです」と思います。私はそうではありません。
答え1
まず、コマンドを実行するシステムがあります。
このシェルコードを使用すると:
cmd and its args
シェルは、リストされているディレクトリー内のcmd
別名、関数、組み込み、および実行可能ファイル(実行権限を持つ通常のファイル)を探します$PATH
(現在の作業ディレクトリーがそのディレクトリーにない限り、$PATH
これは悪い習慣です)。 。
後者の場合、execve()
通常は3つの引数を使用して子プロセスからシステムコールを呼び出します。
- ファイルパス(
/path/to/cmd
) - パラメータリスト(
["cmd", "and", "its", "args", 0]
) var=value
エクスポートされた各変数の文字列のリスト。
execve()
ファイルの種類に応じてファイルを処理する場合:
- ELFバイナリ実行可能ファイルの場合は、そのファイル(またはその一部)をメモリにロード/マッピングし、より多くの共有ライブラリをロードして実行を開始する動的リンカーも可能です。
- で始まる場合
#!
(シェルではない)、行の残りの部分を実行する別のファイルとして解釈し、ファイルパスをコマンドの追加引数として渡します。 (もしそうなら#! foo bar
、同じことをしますexecve("foo", ["foo" or "cmd", "bar", "/path/to/cmd", "and", "its", "args"], env)
)。 - システムはELFに加えてさまざまなデフォルトの実行可能ファイル形式をサポートでき、一部のシステムはインタプリタをファイルの先頭に一致するパターンに関連付けるように設定できます(Linuxのbinfmt_miscを参照)。
プロセスメモリは、プロセス中にほとんど消去され、プロセスが実行可能ファイルexecve()
でコードを実行しているため、決して返されません。
ファイルの種類が認識されない場合は、エラーコードに戻り(失敗表示)execve()
されます。-1
ENOEXEC
この場合、POSIXシェルが必要であり、これをスクリプトとして処理するには、execlp()
C関数(システムコールではない)やenv
/ ...(または通常はPOSIXツールボックスでコマンドを実行するために使用されるすべての項目)find -exec
などが必要sh
です。ランダムに実行されるのを避けるために経験的な方法を使用しているようですsh
。
sh
ほとんどのシェルは shebang があるかのように実行してこれを行います#! /path/to/the/standard/sh -
。一部はPOSIX準拠のsh
実装であるか、サブプロセスでファイル自体を解釈してこれを実行するPOSIX shモードを使用します。
したがって、3つのシナリオの場合:
- shebang-lessの代わりに
script
実行すると機能します。シェルは子プロセスで実行され、既知の形式ではないため、ENOEXECで失敗するため、シェルは(オプションで)どのように見えるかを確認します。内で実行できます。構文(またはバリアント)またはそれ自体で解釈されます(親シェルプロセスは終了するのを待ちます)。./script
script
/usr/bin/script
execve("./script", ["./script", 0], env)
execve()
sh
execve("/bin/sh", ["sh", "-", "./script"])
argv[0]
-
./exe
:shellはexecve("./exe", ["./exe", 0], env)
子プロセスでこれを行い、子プロセスは成功し、親プロセスはそのタスクが終了するのを待ちます。./script
#! /bin/tcsh
shebang:shellを使用すると、execve("./script", ["./script", 0], env)
システムはそれをスクリプトとして認識するため、成功することもできます。パラメータを使用して順番に実行されexecve()
ます。親シェルは通常どおりサブシェルを待ちます。/bin/tcsh
./script
答え2
シェルで次のコマンドを実行すると、
alpha beta gamma
コマンドラインのすべての拡張が完了したら、シェルはalpha
コマンドタイプが何であるかを確認する必要があります。エイリアスまたは定義された関数でない場合は、外部コマンドでなければなりません。この場合、指定されたディレクトリからプログラムを検索しPATH
、見つかったらオペレーティングシステム(fork + exec)を使用して実行します。
スクリプトは行で始める必要はありません#!
。ファイルが実行可能であり、特定のタイプであることを示すヘッダーがない場合は、シェルを使用して処理されます。
GNU/Linux では、execve
このファイルへのシステム呼び出しが失敗します。認識できる形式はありません。このライブラリはexecve
シェルを使用して透過的に他のライブラリを再試行します。strace
何が起こったのかについてのスナップショットは次のとおりです。
32056 execve("./command", ["./command"], 0xbfb71504 /* 27 vars */) = -1 ENOEXEC (Exec format error)
32056 execve("/bin/sh", ["/bin/sh", "./command"], 0xbfb71504 /* 27 vars */) = 0
以下は1行だけを含む./command
ファイルです。echo foo
実行権限があります。
追跡されたプログラムは、execvp
この関数を一度だけ呼び出したと思います。この関数は内部的にexecve
カーネルシステムコールを呼び出します。初めて呼び出すときはスクリプトを直接実行できないため、execvp
今回は/bin/sh
実行可能ファイルを引数0として再試行します。
これは実際にはLinuxだけの問題ではありません。 Linuxの問題でもあります。これはPOSIXで要求され、次のようになります。
一般的な歴史的実装は、execl()、execv()、execle()、およびexecve()関数が実行可能ファイル(シェルスクリプトを含む)として認識されないすべてのファイルに対して[ENOEXEC]エラーを返すことです。 execlp() および execvp() 関数は、これらのファイルが見つかると、そのファイルがシェルスクリプトであると仮定し、そのファイルを解釈することが知られているコマンドソルバーを呼び出します。 POSIXではこれが必要です。
したがって、-suffixed(-searching)関数を#!
使用するアプリケーションには、シェルスクリプトのどの行もディスパッチされません。シェルでこれを行うか、同様のロジックを直接実装する必要があります。 (つまり、シェルは独自の検索を実装してからを使用し、同様の方法でエラーを修復できます。)p
PATH
exec
PATH
execve