「ps ax」が「#!」ヘッダーなしで実行中のbashスクリプトを見つけられないのはなぜですか?


13

このスクリプトを実行すると、殺されるまで実行することを意図しています...

# foo.sh

while true; do sleep 1; done

...を使用して見つけることができませんps ax

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

...ただし#!、スクリプトに共通の " "ヘッダーを追加するだけの場合...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

...その後、同じpsコマンドでスクリプトが検索可能になります...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

これはなぜですか?
これは関連する質問かもしれません。「#」は単なるコメントプレフィックスであり、そうであれば「#! /usr/bin/bash」自体はコメントにすぎないと考えました。しかし、「#!」は単なるコメントよりも大きな意味を持ちますか?


どのUnixを使用していますか?
クサラナナンダ

@Kusalananda-Linux linuxbox 3.11.10-301.fc20.x86_64#1 SMP木12月5日14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

回答:


13

現在の対話型シェルがbashであり、#!-line なしでスクリプトをbash実行すると、スクリプトが実行されます。プロセスはps ax出力にちょうどとして表示されますbash

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

別のターミナルで:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

関連:


関連するセクションはbashマニュアルを形成します:

ファイルが実行可能形式ではなく、ファイルがディレクトリではないためにこの実行が失敗した場合、それはシェルスクリプト、シェルコマンドを含むファイルであると想定されます。 サブシェルが生成されて実行されます。このサブシェルはそれ自体を再初期化するため、新しいシェルがスクリプトを処理するために呼び出されたかのようになりますが、例外は親によって記憶されたコマンドの場所(SHELL BUILTIN COMMANDSの下のハッシュを参照)が子によって保持されることです。

プログラムがで始まるファイルの場合、#!最初の行の残りはプログラムのインタープリターを指定します。 シェルは、この実行可能形式自体を処理しないオペレーティングシステムで指定されたインタープリターを実行します。[...]

これは、-line がない./foo.sh場合にコマンドラインで実行することは、サブシェルのファイルでコマンドを実行することと同じことを意味します。foo.sh#!

$ ( echo "$BASHPID"; while true; do sleep 1; done )

、適切な#!-lineは、例えばを指し/bin/bash、それはやっています

$ /bin/bash foo.sh

私は従うと思いますが、あなたの言うことは2番目の場合にも当てはまります:bashは2番目の場合にもpsスクリプトを実行し/usr/bin/bash ./foo.shます。最初のケースでは、bashがスクリプトを実行しますが、2番目のケースのように、そのスクリプトをフォークされたbash実行可能ファイルに「渡す」必要はありませんか?(もしそうなら、grepへのパイプで
見つけ

@StoneThrow更新された回答を参照してください。
クサラナナンダ

「...新しいプロセスを取得することを除いて」-まあ、どちらの方法でも新しいプロセスを取得しますが$$、サブシェルの場合(echo $BASHPID/ bash -c 'echo $PPID')の古いプロセスを指すことは例外です。
マイケルホーマー

@MichaelHomerあ、ありがとう!更新されます。
クサラナナンダ

12

シェルスクリプトがで始まる場合#!、その最初の行はシェルに関する限りコメントです。ただし、最初の2文字はシステムの別の部分、つまりカーネルにとって意味があります。この2つのキャラクター#!シバンと呼ばれます。シバンの役割を理解するには、プログラムの実行方法を理解する必要があります。

ファイルからプログラムを実行するには、カーネルからのアクションが必要です。これは、execveシステムコールの一部として行われます。カーネルは、ファイルのアクセス許可を確認し、呼び出しプロセスで現在実行中の実行可能ファイルに関連付けられているリソース(メモリなど)を解放し、新しい実行可能ファイルにリソースを割り当て、新しいプログラムに制御を転送する必要があります私は言及しません)。execveシステムコールは、現在実行中のプロセスのコードを置き換えます。fork新しいプロセスを作成するための別のシステムコールがあります。

これを行うには、カーネルが実行可能ファイルの形式をサポートする必要があります。このファイルには、カーネルが理解できるように編成されたマシンコードが含まれている必要があります。シェルスクリプトにはマシンコードが含まれていないため、この方法で実行することはできません。

shebangメカニズムにより、カーネルはコードを別のプログラムに解釈するタスクを延期できます。カーネルは、実行可能ファイルがで始まることを認識#!すると、次の数文字を読み取り、ファイルの最初の行(先頭#!とオプションのスペースを除く)を別のファイルへのパスとして解釈します(引数についてはここでは説明しません) )。カーネルがファイルを実行するように指示さ/my/scriptれ、ファイルが行#!/some/interpreterで始まることがわかると、カーネルは/some/interpreter引数で実行されます/my/script。その後、それが実行するスクリプトファイルであるかどう/some/interpreterかを判断し/my/scriptます。

ファイルにカーネルが理解できる形式のネイティブコードが含まれておらず、シバンで始まらない場合はどうなりますか?それでは、ファイルは実行可能ではなく、execveシステムコールはエラーコードENOEXEC(実行可能形式エラー)で失敗します。

これで話は終わりかもしれませんが、ほとんどのシェルはフォールバック機能を実装しています。カーネルがを返すENOEXEC場合、シェルはファイルのコンテンツを調べ、ファイルがシェルスクリプトのように見えるかどうかを確認します。シェルは、ファイルがシェルスクリプトのように見えると判断した場合、それ自体を実行します。これを行う方法の詳細は、シェルによって異なります。ps $$スクリプトに追加することで、何が起こっているかを見ることができます。さらにstrace -p1234 -f -eprocess、1234がシェルのPIDであるプロセスを見ると、何が起こっているのかを確認できます。

bashでは、このフォールバックメカニズムはを呼び出すことで実装されますがfork、ではありませんexecve。子bashプロセスは、内部状態を自動的にクリアし、新しいスクリプトファイルを開いて実行します。したがって、スクリプトを実行するプロセスは、元のbashコードイメージと、bashを最初に起動したときに渡された元のコマンドライン引数を引き続き使用しています。ATT kshは同じように動作します。

% bash --norc
bash-4.3$ ./foo.sh 
  PID TTY      STAT   TIME COMMAND
21913 pts/2    S+     0:00 bash --norc

対照的に、ダッシュは、引数として渡されたスクリプトへのパスでENOEXEC呼び出し/bin/shて反応します。つまり、ダッシュからシバンレススクリプトを実行すると、スクリプトにのようなシバン行が含まれているかのように動作し#!/bin/shます。Mkshとzshは同じように動作します。

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh

わかりやすい答え。1つの質問RE:あなたが説明したフォールバック実装:子bashはフォークされているargv[]ので、親と同じ配列にアクセスできると思います。だから、これは子供が明示的な引数として元のスクリプトを渡されない理由です(それがなぜそれがgrepによって見つけられないのですか)-それは正確ですか?
StoneThrow

1
実際にカーネルのシバン動作をオフにすることができます(BINFMT_SCRIPTモジュールはこれを制御し、通常はカーネルに静的にリンクされていますが、削除/モジュール化できます)。 。この可能性の回避策として、bash設定フラグ(HAVE_HASH_BANG_EXEC)を用意してください!
ErikF

2
@StoneThrow子bashが「元のコマンドライン引数を知っている」ことは、変更しないためです。プログラムはps、コマンドライン引数として報告するものを変更できますが、ポイントまでしかありません。既存のメモリバッファを変更する必要があり、このバッファを拡大することはできません。そのため、bash argvがスクリプトの名前を追加するように変更しようとした場合、常に機能するとは限りません。子にはexecveシステムコールがないため、子は「引数を渡されません」。実行を継続するのと同じbashプロセスイメージです。
ジル 'SO-悪であるのをやめる'

-1

最初の場合、スクリプトは現在のシェルから分岐した子によって実行されます。

最初に実行してecho $$から、親プロセスIDとしてシェルのプロセスIDを持つシェルを見てください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.