👅

bash スクリプトの実行中上書き動作について

2 min read

はじめに

本記事は、bash の実行中スクリプトファイル上書きでどの様に動作するのかを個人的な興味の上で確認するのが目的であり、下記の件について言及する物ではありません。

https://www.iimc.kyoto-u.ac.jp/ja/whatsnew/information/detail/211228056999.html

bash スクリプトファイル上書きの動作

https://qiita.com/kitsuyui/items/d0048eeaa50293a92a60

上記の内容によると、実行中に 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 の実装確認

https://savannah.gnu.org/git/?group=bash

evalstring.cparse_and_execute でコマンドが処理されており、input.cwith_input_from_buffered_stream で読み込みの準備が行われている。バッファの読み込みの本体は y.tab.c つまりパーサから直接呼ばれており、このパーサは fgets(2) で読み込まれつつ実行される為、一括でファイルが読み込まれている訳ではない。

while/do でループ実行した際に、ファイルを書き換えられたら戻り先はどうなるか、についてはスクリプトはバッファ付きで読み込まれており、そのバッファがファイルシステムから読み直していなければ同じループが再現される。なので done を見つけた際の戻り先はメモリ上にある。

これらは bash がうんぬんというよりも、ファイルシステムを I/O する際にそれがアトミックであるかどうか、というだけの話。同じ様な事をしているのであれば、どのシェルや言語処理系でも起きうる。

昔の php はパースした結果を AST として保持していなかったので、bash と同じく、YACC のパーサに直接処理が実装されていたのを思い出した。

おわりに

bash のコードは何度か読んだ事があったが、スクリプトの読み込み周りは読んだ事が無かったので、ガードが掛かってない様を見れて良かったし、よりいっそうスクリプト処理系での実行スクリプトファイルの上書きが怖くなった。

繰り返すが、これは bash の問題ではなく、I/O がアトミックではない事で起きる問題であり、この問題を回避しつつスクリプトを入れ替えるのであれば、スクリプトが停止している事を確認するしかない。

この記事に贈られたバッジ

Discussion

ログインするとコメントできます