web-dev-qa-db-ja.com

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

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

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

つまり、シェルスクリプトを実行し、実行が完了する前にそれを削除すると、実行が終了するか、そのまま続行されますか?

25
Registered User

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に関するこのU&L Q&Aで見ることができます。タイトルは grep/egrepは毎回どのくらいのテキストを消費しますか?

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

$ ( 
    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や他のシェルは、ファイル全体を読み込むのではなく、ブロック単位で読み込む可能性があります。

参考文献

35
slm

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

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

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

これは、単純な構造、つまりプレーンコマンドのスイート用です。

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

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

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

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

11
jlliagre

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

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

7
vonbrand

「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を削除するだけです。

4
user62916