Linuxはシェルスクリプトをどのように処理しますか?


22

この質問では、bashシェルスクリプトを考えてみましょう。ただし、この質問はすべてのタイプのシェルスクリプトに適用できる必要があります。

誰かがシェルスクリプトを実行すると、Linuxはすべてのスクリプトを一度に(おそらくメモリに)ロードしますか、それともスクリプトコマンドを1つずつ読み込みますか(行ごと)?

言い換えると、シェルスクリプトを実行し、実行が完了する前に削除すると、実行は終了しますか、それともそのまま続行しますか?


3
それを試してみてください。(それは続きます。)
devnull 14年

1
@devnullには実際に興味深い質問があります。確かに、テストを継続するかどうかは簡単ですが、バイナリファイル(メモリにロードされる)とシェバン行のあるスクリプト、またはシェバン行のないスクリプトには違いがあります。
テルドン


23
実行中にシェルスクリプトを削除するという実際の目的のために、一度に読み込まれるか、行ごとに読み込まれるかは問題ではありません。Unixでは、iノードは、最後に開いたファイルが閉じられるまで、実際には削除されません(どのディレクトリからもリンクがない場合でも)。つまり、実行中にシェルがシェルスクリプトを1行ずつ読み込んでも、削除しても安全です。唯一の例外は、シェルが毎回シェルスクリプトを閉じて再度開く種類である場合ですが、それを行うと、はるかに大きな(セキュリティ)問題が発生します。
クリスジェスターヤング14年

回答:


33

使用straceすると、実行時にシェルスクリプトがどのように実行されるかを確認できます。

このシェルスクリプトがあるとします。

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

を使用して実行するstrace

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

strace.logファイル内を見てみると、次のことがわかります。

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

ファイルが読み込まれると、実行されます。

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

上記では、スクリプト全体が単一のエンティティとして読み込まれ、その後実行されるように見えることが明確にわかります。そのため、少なくともBashがファイルを読み込んで実行する場合、「出現」します。スクリプトの実行中に編集できると思いますか?

注:しないでください!実行中のスクリプトファイルを台無しにしない理由を理解するために読んでください。

他の通訳はどうですか?

しかし、あなたの質問は少しずれています。必ずしもファイルのコンテンツをロードするのはLinuxではなく、コンテンツをロードするのはインタープリターです。したがって、ファイルを完全にロードするか、一度にブロックまたは行単位でロードするかは、インタープリターの実装方法次第です。

では、なぜファイルを編集できないのでしょうか?

ただし、はるかに大きなスクリプトを使用すると、上記のテストが少し誤解を招くことに気付くでしょう。実際、ほとんどのインタープリターはファイルをブロック単位でロードします。これは、ファイルのブロックをロードし、それを処理してから別のブロックをロードするUnixツールの多くでかなり標準的なものです。この動作はgrep、「grep / egrepは毎回どれだけのテキストを消費するか」というタイトルのについて前に書いたこのU&LのQ&Aで見ることができます

次のシェルスクリプトを作成するとします。

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

このファイルの結果:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

次の種類のコンテンツが含まれます。

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

これで、上記と同じ手法を使用してこれを実行するとstrace

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

ファイルは8KB単位で読み込まれるため、Bashや他のシェルはファイルをブロック単位で読み込むのではなく、ファイル全体を読み込まない可能性があります。

参照資料


@terdon-確かにその前にQ&Aを見たことを覚えています。
slm

5
40バイトのスクリプトでは、確かに、1ブロックで読み取られます。> 8kBスクリプトで試してください。
ジル 'SO-悪であるのをやめる' 14年

試したことはありませんが、すべてのプロセスが削除されたファイルに関連付けられたファイル記述子を閉じるまで、ファイルの削除は実際には行われないため、bashは削除されたファイルからの読み取りを続ける可能性があります
ファリッドノウリネシャット14年

@Gilles-はい、例を追加しました。
slm

2
この動作はバージョンに依存します。bashバージョン3.2.51(1)-releaseでテストしたところ、現在の行を超えてバッファリングしないことがわかりました(このstackoverflow answerを参照)。
ゴードンデイヴィソン14年

11

これは、OS依存よりもシェル依存です。

バージョンに応じて、ksh8kまたは64kバイトブロック単位でオンデマンドでスクリプトを読み取ります。

bashスクリプトを1行ずつ読み取ります。ただし、ファクト行の長さは任意であるため、毎回8176バイトを解析する次の行の先頭から読み取ります。

これは単純な構造、つまり一連の単純なコマンド用です。

ループ、スイッチ、ヒアドキュメント、括弧で囲まれたサブシェル、関数定義など、および上記の任意の組み合わせのようなシェル構造化コマンドが使用される場合受け入れられる回答が考慮されない場合)、シェルインタープリターは読み取ります構文の最後まで、構文エラーがないことを最初に確認します。for/do/donecase/esac

同じコードが何度も何度も読み取られる可能性があるため、これはやや非効率的ですが、このコンテンツが通常キャッシュされるという事実によって軽減されます。

シェルインタープリターが何であれ、実行中にシェルスクリプトを変更することは非常に賢明ではありません。シェルはスクリプトの任意の部分を自由に再読み込みできるため、同期していないと予期しない構文エラーが発生する可能性があります

あまりにも大きなスクリプト構造を格納できない場合、bashはセグメンテーション違反でクラッシュする可能性があることに注意してください。ksh93は問題なく読み取ることができます。


7

これは、スクリプトを実行するインタープリターの動作方法によって異なります。カーネルは、実行するファイルがで始まることに気付くだけで#!、基本的に行の残りをプログラムとして実行し、引数として実行可能ファイルを提供します。そこにリストされたインタープリターがそのファイルを1行ずつ読み取ります(対話型シェルが入力内容を処理するように)、それが得られます(ただし、複数行のループ構造が読み取られ、繰り返しのために保持されます)。インタプリタがファイルをメモリに丸lurみし、それを処理する(おそらくPerlやPytonのように中間表現にコンパイルする)場合、ファイルは実行前に完全に読み取られます。

その間にファイルを削除すると、インタープリターがファイルを閉じるまでファイルは削除されません(常に、最後の参照がディレクトリエントリまたはプロセスを開いたままにするとファイルは消えます)。


4

「x」ファイル:

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

実行:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRCファイルは、プロセスが開いている限り削除されません。削除すると、指定されたDIRENTが削除されます。

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