web-dev-qa-db-ja.com

シェルでファイルを実行すると、正確にはどうなりますか?

それで、私はこれをよく理解していると思いましたが、テストを実行しただけで(誰かと反対した会話に応じて)、私の理解に欠陥があることがわかりました...

できるだけ詳しくシェルでファイルを実行すると、正確にはどうなりますか?つまり、シェルに_./somefile some arguments_と入力してReturnキーを押すと(そしてsomefileがcwdに存在し、somefileに対する読み取りと実行のアクセス許可がある場合)、次のようになります。フードの下で起こりますか?

私は考えました答えは:

  1. シェルはexecへのシステムコールを作成し、somefileへのパスを渡します
  2. カーネルはsomefileを調べ、ファイルの マジックナンバー を調べて、プロセッサが処理できる形式かどうかを判断します
  3. マジックナンバーが、ファイルがプロセッサが実行できる形式であることを示している場合、
    1. 新しいプロセスが作成されます(プロセステーブルにエントリがあります)
    2. somefileはメモリに読み込まれるか、マップされます。スタックが作成され、実行がsomefileのコードのエントリポイントにジャンプします。ARGVはパラメーターの配列に初期化されます(a _char**_、_["some","arguments"]_)
  4. マジックナンバーが Shebang の場合、exec()は上記のように新しいプロセスを生成しますが、使用される実行可能ファイルは、Shebangによって参照されるインタープリターです(例_/bin/bash_または_/bin/Perl_)およびsomefileSTDINに渡されます
  5. ファイルに有効なマジックナンバーがない場合、「無効なファイル(不正なマジックナンバー):Exec形式エラー」のようなエラーが発生します。

しかし、誰かが私に、ファイルがプレーンテキストの場合、シェルはコマンドを実行しようとすると言った(まるで_bash somefile_を入力したかのように)。信じられませんでしたが、試してみただけで正解でした。ですから、ここで実際に何が起こっているのかについて、いくつかの誤解が明確にあり、その仕組みを理解したいと思います。

シェルでファイルを実行すると、正確にはどうなりますか? (詳細は合理的です...)

34
Josh

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
_

ご覧のとおり、私が使用していたシェルzshlessから始まり、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 で指摘されています)。

うまくいけば、どこかでディスクファイルに定義されている簡単なコマンドを実行します。

  1. fork ()
  2. パイプを接続する
  3. コマンドを調べる
  4. リダイレクトを行う
  5. execve ()
  6. execveが失敗した場合は、ファイルに実行可能モードが設定されているかどうかを確認してください。その場合、それがディレクトリではない場合、その内容をシェルスクリプトとして実行します。

POSIXは the exec(3) functions と呼ばれる関数のセットを定義します。これはexecve(2)をラップし、この機能も提供します。詳細は mur の回答を参照してください。 Linuxでは、少なくともこれらの関数はカーネルではなくCライブラリによって実装されます。

34
Stephen Kitt

一部、これは、使用される特定のexecファミリー関数に依存します。 execveStephen 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関数ファミリーの動作にどのように影響するかです。以下は、実行されるアクションの説明です。

  1. プロセスイメージファイルがこのシステムの有効な実行可能ファイル(実行可能で有効な形式で、適切な特権を持つ)である場合、システムはそのファイルを実行します。

  2. プロセスイメージファイルに適切な特権があり、このシステムでは実行可能であるが有効ではない形式(別のアーキテクチャーで認識されるバイナリーなど)の場合、これはエラーであり、errnoは[EINVAL]に設定されます(後述のRATIONALEを参照) [EINVAL])。

  3. プロセスイメージファイルに適切な権限があるが、他の方法では認識されない場合:

    1. これがexeclp()またはexecvp()の呼び出しである場合、プロセスイメージファイルがシェルスクリプトであると想定して、コマンドインタープリターを呼び出します

    2. これが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を使用して同じロジックを実装します。

12
muru

これは、ファイルexecute_cmd.cbashソースからのコメントとして、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.  

その場合、それがディレクトリではない場合は、その内容をシェルスクリプトとして実行します。

6
taliezin

それはシェルスクリプトとして実行されますnotソースです(たとえば、実行されたファイルに設定された変数は外部に影響しません)。 one Shellとone実行可能形式があったときに、霧の過去からの痕跡と思われます。実行可能ファイルではなく、シェルスクリプトである必要があります。

0
vonbrand