moreutilsのspongeコマンドのしくみ
こんにちは。脳がスポンジのまるりんです。
コマンドラインでファイルを処理し、結果を同一ファイルに出力したいと思ったことはないでしょうか。 たとえばテキストファイルに行番号を振るために以下を実行します。するとファイルの中身が空になってしまいます。
$ cat test.txt
aaa
bbb
ccc
$ cat -n test.txt > test.txt
$ cat test.txt
出力のリダイレクトによりファイルの長さをゼロにする処理が行われてしまうからです。 回避策として適当なファイル名に出力しリネームする方法もあるのですが、今回はmoreutilsのspongeコマンドを使ってみます。 ちなみにspongeの由来は標準入力をスポンジ(吸収)してファイルに書き込むという意味のようです。
$ cat -n test.txt | sponge test.txt
$ cat test.txt
1 aaa
2 bbb
3 ccc
さて、spongeですがどのようなしくみになっているのでしょうか。 spongeのソースコードを読むと次のことがわかります。
char const * const template="%s/sponge.XXXXXX";
trapsignals();
cs = cs_enter();
tmpdir = getenv("TMPDIR");
if (tmpdir == NULL)
tmpdir = "/tmp";
/* Subtract 2 for `%s' and add 1 for the trailing NULL. */
tmpname=malloc(strlen(tmpdir) + strlen(template) - 2 + 1);
if (! tmpname) {
perror("failed to allocate memory");
exit(1);
}
sprintf(tmpname, template, tmpdir);
mask=umask(077);
tmpfd = mkstemp(tmpname);
umask(mask);
atexit(onexit_cleanup); // solaris on_exit(onexit_cleanup, 0);
https://github.com/stigtsp/moreutils/blob/master/sponge.c#L247
どうやら環境変数TMPDIRか、それがなければ/tmpに一時ファイルを作成し、 そのファイルを引数で与えられたファイル名にリネーム(パーミッションの移行処理含む)しているようです。 また、atexit()に渡されるonexit_cleanup関数内で一時ファイルを削除しています。
本当に一時ファイルを作成しているのか確認してみます。 まず適当なコンテナの中に入って/tmpに一時ファイルが作られるのか監視します。
$ docker run -it --rm ubuntu bash
# unset TMPDIR
# perl -e 'for(;;){system("cat /tmp/sponge.* 2>/dev/null");}'
そしてもうひとつのシェルを起動し、同一コンテナに入りspongeを動かしてみます。 例では一時ファイルがキャプチャされる前に削除される可能性があるのでspongeを100回実行しています。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
57f51b384378 ubuntu "bash" About a minute ago Up About a minute wizardly_mendeleev
$ docker exec -it wizardly_mendeleev bash
# apt update
# apt install -y moreutils
# cat > test.txt
abc
# for i in {1..100} ; do cat test.txt | sponge test.txt ; done
最初に起動したシェルを確認すると以下の出力が行われていました。
# perl -e 'for(;;){system("cat /tmp/sponge.* 2>/dev/null");}'
abc
abc
^C^C^C^C^C^C^C # ループから抜ける
# ls -a /tmp
. ..
spongeが作った一時ファイルをキャプチャできました。/tmpは空になっているため、一時ファイルが削除されたことも確認できました。