この質問では、bashシェルスクリプトを考えてみましょう。ただし、この質問はすべてのタイプのシェルスクリプトに適用できる必要があります。
誰かがシェルスクリプトを実行すると、Linuxはすべてのスクリプトを一度に(おそらくメモリに)ロードしますか、それともスクリプトコマンドを1つずつ読み込みますか(行ごと)?
言い換えると、シェルスクリプトを実行し、実行が完了する前に削除すると、実行は終了しますか、それともそのまま続行しますか?
この質問では、bashシェルスクリプトを考えてみましょう。ただし、この質問はすべてのタイプのシェルスクリプトに適用できる必要があります。
誰かがシェルスクリプトを実行すると、Linuxはすべてのスクリプトを一度に(おそらくメモリに)ロードしますか、それともスクリプトコマンドを1つずつ読み込みますか(行ごと)?
言い換えると、シェルスクリプトを実行し、実行が完了する前に削除すると、実行は終了しますか、それともそのまま続行しますか?
回答:
使用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や他のシェルはファイルをブロック単位で読み込むのではなく、ファイル全体を読み込まない可能性があります。
これは、OS依存よりもシェル依存です。
バージョンに応じて、ksh
8kまたは64kバイトブロック単位でオンデマンドでスクリプトを読み取ります。
bash
スクリプトを1行ずつ読み取ります。ただし、ファクト行の長さは任意であるため、毎回8176バイトを解析する次の行の先頭から読み取ります。
これは単純な構造、つまり一連の単純なコマンド用です。
ループ、スイッチ、ヒアドキュメント、括弧で囲まれたサブシェル、関数定義など、および上記の任意の組み合わせのようなシェル構造化コマンドが使用される場合(受け入れられる回答が考慮されない場合)、シェルインタープリターは読み取ります構文の最後まで、構文エラーがないことを最初に確認します。for/do/done
case/esac
同じコードが何度も何度も読み取られる可能性があるため、これはやや非効率的ですが、このコンテンツが通常キャッシュされるという事実によって軽減されます。
シェルインタープリターが何であれ、実行中にシェルスクリプトを変更することは非常に賢明ではありません。シェルはスクリプトの任意の部分を自由に再読み込みできるため、同期していないと予期しない構文エラーが発生する可能性があります
あまりにも大きなスクリプト構造を格納できない場合、bashはセグメンテーション違反でクラッシュする可能性があることに注意してください。ksh93は問題なく読み取ることができます。
これは、スクリプトを実行するインタープリターの動作方法によって異なります。カーネルは、実行するファイルがで始まることに気付くだけで#!
、基本的に行の残りをプログラムとして実行し、引数として実行可能ファイルを提供します。そこにリストされたインタープリターがそのファイルを1行ずつ読み取ります(対話型シェルが入力内容を処理するように)、それが得られます(ただし、複数行のループ構造が読み取られ、繰り返しのために保持されます)。インタプリタがファイルをメモリに丸lurみし、それを処理する(おそらくPerlやPytonのように中間表現にコンパイルする)場合、ファイルは実行前に完全に読み取られます。
その間にファイルを削除すると、インタープリターがファイルを閉じるまでファイルは削除されません(常に、最後の参照がディレクトリエントリまたはプロセスを開いたままにするとファイルは消えます)。
「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が削除されます。