それで、私はこれをよく理解していると思いましたが、テストを実行しただけで(誰かと反対した会話に応じて)、私の理解に欠陥があることがわかりました...
できるだけ詳しくシェルでファイルを実行すると、正確にはどうなりますか?つまり、シェルに_./somefile some arguments
_と入力してReturnキーを押すと(そしてsomefile
がcwdに存在し、somefile
に対する読み取りと実行のアクセス許可がある場合)、次のようになります。フードの下で起こりますか?
私は考えました答えは:
exec
へのシステムコールを作成し、somefile
へのパスを渡しますsomefile
を調べ、ファイルの マジックナンバー を調べて、プロセッサが処理できる形式かどうかを判断しますsomefile
はメモリに読み込まれるか、マップされます。スタックが作成され、実行がsomefile
のコードのエントリポイントにジャンプします。ARGV
はパラメーターの配列に初期化されます(a _char**
_、_["some","arguments"]
_)exec()
は上記のように新しいプロセスを生成しますが、使用される実行可能ファイルは、Shebangによって参照されるインタープリターです(例_/bin/bash
_または_/bin/Perl
_)およびsomefile
がSTDIN
に渡されますしかし、誰かが私に、ファイルがプレーンテキストの場合、シェルはコマンドを実行しようとすると言った(まるで_bash somefile
_を入力したかのように)。信じられませんでしたが、試してみただけで正解でした。ですから、ここで実際に何が起こっているのかについて、いくつかの誤解が明確にあり、その仕組みを理解したいと思います。
シェルでファイルを実行すると、正確にはどうなりますか? (詳細は合理的です...)
Linuxでの「プログラムの実行方法」に対する決定的な答えは、 LWN.net というタイトルの記事のペアです。驚くほど十分な プログラムの実行方法 および Howプログラムが実行されます:ELFバイナリ 。最初の記事では、スクリプトについて簡単に説明しています。 (厳密に言えば、決定的な答えはソースコードにありますが、これらの記事は読みやすく、ソースコードへのリンクを提供します。)
少し実験を行ったところ、ほとんど問題なく動作し、Shebangを使用せずにコマンドの単純なリストを含むファイルの実行をシェルで処理する必要があることがわかりました。 execve(2) のマンページには、テストプログラムexecveのソースコードが含まれています。これを使用して、シェルなしで何が起こるかを確認します。まず、テストスクリプト_testscr1
_を記述します。
_#!/bin/sh
pstree
_
もう1つは_testscr2
_のみで、
_pstree
_
両方を実行可能にして、両方がシェルから実行されることを確認します。
_chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
_
execve
を使用して、もう一度試してください(現在のディレクトリにビルドしたと想定):
_./execve ./testscr1
./execve ./testscr2
_
_testscr1
_は引き続き実行されますが、_testscr2
_は生成します
_execve: Exec format error
_
これは、シェルが_testscr2
_を異なる方法で処理することを示しています。スクリプト自体は処理しませんが、それでも_/bin/sh
_を使用して処理します。これは、_testscr2
_をless
にパイプすることで確認できます。
_./testscr2 | less -ppstree
_
私のシステムでは、
_ |-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
_
ご覧のとおり、私が使用していたシェルzsh
はless
から始まり、2番目のシェルはプレーンsh
(私のシステムではdash
)です。 )、pstree
を実行したスクリプトを実行します。 zsh
では、これはzexecve
によって処理されます _Src/exec.c
_ :シェルはexecve(2)
を使用してコマンドの実行を試みます。失敗した場合は、ファイルを読み取ってShebangがあるかどうかを確認し、それに応じて処理します(カーネルも実行します)。失敗した場合、sh
でファイルを実行しようとします。ファイルからゼロバイトを読み取っていません:
_ for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
_
bash
は同じ動作をし、 _execute_cmd.c
_ に実装され、役立つコメントが付けられています( taliezin で指摘されています)。
うまくいけば、どこかでディスクファイルに定義されている簡単なコマンドを実行します。
fork ()
- パイプを接続する
- コマンドを調べる
- リダイレクトを行う
execve ()
execve
が失敗した場合は、ファイルに実行可能モードが設定されているかどうかを確認してください。その場合、それがディレクトリではない場合、その内容をシェルスクリプトとして実行します。
POSIXは the exec(3)
functions と呼ばれる関数のセットを定義します。これはexecve(2)
をラップし、この機能も提供します。詳細は mur の回答を参照してください。 Linuxでは、少なくともこれらの関数はカーネルではなくCライブラリによって実装されます。
一部、これは、使用される特定のexec
ファミリー関数に依存します。 execve
、 Stephen Kitt が詳細に示したように、正しいバイナリ形式のファイルまたは適切なシバンで始まるスクリプトのみを実行します。
ただし、、execlp
およびexecvp
はさらに一歩進みます。シェバンが正しくなかった場合、ファイルは_/bin/sh
_で実行されますLinux。から _man 3 exec
_ :
_Special semantics for execlp() and execvp()
The execlp(), execvp(), and execvpe() functions duplicate the actions
of the Shell in searching for an executable file if the specified
filename does not contain a slash (/) character.
…
If the header of a file isn't recognized (the attempted execve(2)
failed with the error ENOEXEC), these functions will execute the
Shell (/bin/sh) with the path of the file as its first argument. (If
this attempt fails, no further searching is done.)
_
これは [〜#〜] posix [〜#〜] によっていくらかサポートされています(強調は私のものです):
標準の開発者によって指摘された混乱の潜在的な原因の1つは、プロセスイメージファイルの内容がexec関数ファミリーの動作にどのように影響するかです。以下は、実行されるアクションの説明です。
プロセスイメージファイルがこのシステムの有効な実行可能ファイル(実行可能で有効な形式で、適切な特権を持つ)である場合、システムはそのファイルを実行します。
プロセスイメージファイルに適切な特権があり、このシステムでは実行可能であるが有効ではない形式(別のアーキテクチャーで認識されるバイナリーなど)の場合、これはエラーであり、errnoは[EINVAL]に設定されます(後述のRATIONALEを参照) [EINVAL])。
プロセスイメージファイルに適切な権限があるが、他の方法では認識されない場合:
これがexeclp()またはexecvp()の呼び出しである場合、プロセスイメージファイルがシェルスクリプトであると想定して、コマンドインタープリターを呼び出します
これがexeclp()またはexecvp()の呼び出しでない場合、エラーが発生し、errnoが[ENOEXEC]に設定されます。
これはコマンドインタープリターの取得方法を指定していませんが、エラーが発生する必要があることを指定していません。したがって、Linux開発者はそのようなファイルを_/bin/sh
_で実行することを許可したと思います(または、これはすでに一般的な慣行であり、彼らはちょうどそれに倣いました)。
FWIW exec(3)
)の---(FreeBSDマンページ でも同様の動作について言及しています。
_ Some of these functions have special semantics.
The functions execlp(), execvp(), and execvP() will duplicate the actions
of the Shell in searching for an executable file if the specified file
name does not contain a slash ``/'' character.
…
If the header of a file is not recognized (the attempted execve()
returned ENOEXEC), these functions will execute the Shell with the path
of the file as its first argument. (If this attempt fails, no further
searching is done.)
_
ただし、一般的なシェルは、おそらく環境をより細かく制御するために、execlp
またはexecvp
を直接使用しません。これらはすべて、execve
を使用して同じロジックを実装します。
これは、ファイルexecute_cmd.c
のbash
ソースからのコメントとして、Stephen Kittの回答に追加される可能性があります。
うまくいけば、どこかでディスクファイルに定義されている簡単なコマンドを実行します。
1. fork () 2. connect pipes 3. look up the command 4. do redirections 5. execve () 6. If the execve failed, see if the file has executable mode set.
その場合、それがディレクトリではない場合は、その内容をシェルスクリプトとして実行します。
それはシェルスクリプトとして実行されますnotソースです(たとえば、実行されたファイルに設定された変数は外部に影響しません)。 one Shellとone実行可能形式があったときに、霧の過去からの痕跡と思われます。実行可能ファイルではなく、シェルスクリプトである必要があります。