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は空になっているため、一時ファイルが削除されたことも確認できました。