bash スクリプトの実行中上書き動作について
はじめに
本記事は、bash の実行中スクリプトファイル上書きでどの様に動作するのかを個人的な興味の上で確認するのが目的であり、下記の件について言及する物ではありません。
bash スクリプトファイル上書きの動作
上記の内容によると、実行中に echo foo
から echo bar
に変更すると bar
が表示されるというもの。
#!/bin/sh
sleep 30
echo foo
手元で試した所、bar
は表示されなかった。試しに SHEBANG を bash
に変更してみたが再現しなかった。
ただし実行中に下記のファイルを cp
で上書きすると再現した。
#!/bin/sh
sleep 30
echo bar
また、cp
ではなく mv
を使うと再現しなかった。さらに echo bar
が書かれたスクリプトを tar
でアーカイブしておき、実行中に x
で上書きした所、再現しなかった。まとめると以下の通り。
やった内容 | 再現ありなし |
---|---|
vim で上書き | 再現せず |
cp で上書き | 再現した |
tar で上書き | 再現せず |
この事から推測すると下記の事が言える。
bash はスクリプト実行時に一括読み込みを行っておらず、逐次でファイルを読み込んでいる為、同じ i-node のファイルを続きから読み込む。既に開かれたファイルディスクリプタは i-node が異なる場合は古いファイルから読み続けられる為、再現しない。
Vim は保存時、一旦テンポラリファイルにファイルを書き出し、リネームする事で保存を行っているのが理由であろう。実際に Vim で
:set backupcopy=yes
を設定してから再度試した所 bar
が表示された。backupcopy
は編集中のファイルによって自動で判別する auto
がデフォルトになっている為、試す際には明示的に yes
に設定しないといけない。
bash の実装確認
evalstring.c
の parse_and_execute
でコマンドが処理されており、input.c
の with_input_from_buffered_stream
で読み込みの準備が行われている。バッファの読み込みの本体は y.tab.c
つまりパーサから直接呼ばれており、このパーサは fgets(2)
で読み込まれつつ実行される為、一括でファイルが読み込まれている訳ではない。
while/do
でループ実行した際に、ファイルを書き換えられたら戻り先はどうなるか、についてはスクリプトはバッファ付きで読み込まれており、そのバッファがファイルシステムから読み直していなければ同じループが再現される。なので done
を見つけた際の戻り先はメモリ上にある。
これらは bash がうんぬんというよりも、ファイルシステムを I/O する際にそれがアトミックであるかどうか、というだけの話。同じ様な事をしているのであれば、どのシェルや言語処理系でも起きうる。
昔の php はパースした結果を AST として保持していなかったので、bash と同じく、YACC のパーサに直接処理が実装されていたのを思い出した。
おわりに
bash のコードは何度か読んだ事があったが、スクリプトの読み込み周りは読んだ事が無かったので、ガードが掛かってない様を見れて良かったし、よりいっそうスクリプト処理系での実行スクリプトファイルの上書きが怖くなった。
繰り返すが、これは bash の問題ではなく、I/O がアトミックではない事で起きる問題であり、この問題を回避しつつスクリプトを入れ替えるのであれば、スクリプトが停止している事を確認するしかない。
Discussion