mktemp入門|一時ファイルを安全に作って確実に片づける

mktemp入門|一時ファイルを安全に作って確実に片づける

シェルスクリプトの途中で、ちょっとした中間ファイルを置きたくなる場面があります。

私はかつて、そこに /tmp/work.txt のような固定の名前を平気で書いていました。

動くには動くのですが、同じスクリプトを2つ同時に走らせた瞬間、中身が静かに混ざる。

その事故を根本から防ぐのが mktemp というコマンドです。

この記事では、基本形から後片付けまでを、手元で実行した出力に合わせて整理しました。

固定の名前が危ない理由

まず、なぜ自分で名前を決めるとまずいのかを押さえます。

固定名の一時ファイルには、同時実行で中身が混ざるという分かりやすい弱点があるからです。

同じスクリプトが2つ動けば、両方が同じパスへ書き込み、片方の結果がもう片方に上書きされてしまう。

ではプロセスIDを混ぜて work.$$ のようにすればよいかというと、これも万全ではありません。

プロセスIDは値の範囲が限られていて再利用されますし、名前が推測できること自体が共有ディレクトリでは弱点になります。

誰でも書き込める /tmp では、先回りして同じ名前のリンクを置かれる筋道が残るのです。

mktemp は「名前を決める」「衝突しないことを確かめる」「その場で作る」を、まとめてひとつの操作として実行してくれます。

mktemp の基本形

使い方は拍子抜けするほど簡単で、引数なしで叩くだけでも成立。

$ f=$(mktemp)
$ echo "$f"
/tmp/tmp.rodQK1t7XU

作られたパスが標準出力に1行だけ返るので、そのまま変数へ受け取れる。

置き場所の既定は環境変数 TMPDIR で、設定が無ければ /tmp。

CI やコンテナで書き込み先を別ボリュームへ寄せたいときは、この TMPDIR を差し替えるだけで足ります。

作られたファイルは本人だけが読み書きでき、グループとその他には何の権限も付かない状態になります(GNU coreutils のマニュアルに明記されています)。

本記事の実行例は、Windows 上の Git Bash(MINGW64・GNU coreutils 8.32・bash 5.2.37)で確かめました。

ただしこの環境は権限の扱いを Windows 側でエミュレートしているため、ls の表示は 644 になります。

権限まわりの確認だけは Linux 実機で行ってください

名前と置き場所を指定する

あとから中身を見返したい一時ファイルは、名前の形を自分で決められます。

$ mktemp /tmp/demo/report.XXXXXX
/tmp/demo/report.oJkbrB

末尾に並べた X の数だけランダムな文字へ置き換わる仕組みで、最後の要素に X を3つ以上並べるのが決まりです。

足りないとその場で怒られます。

$ mktemp /tmp/demo/short.XX
mktemp: too few X's in template '/tmp/demo/short.XX'

置き場所だけ変えたいなら -p が使えて、テンプレートは名前の部分だけで済む。

$ mktemp -p ./sub run.XXXXXX
./sub/run.XnOqoH
$ mktemp --suffix=.json ./data.XXXXXX
./data.f6Plo6.json

拡張子を後ろに付けたいときは –suffix が効くので、jq に渡す JSON なども扱いやすくなります。

なお置き場所の指定には -t もありますが、マニュアルに非推奨と書かれているので、これから書くなら -p を選ぶのが無難。

複数のファイルを扱うなら、-d で一時ディレクトリごと確保するほうが後始末まで一息で済みます。

$ d=$(mktemp -d)
$ echo "$d"
/tmp/tmp.KT6AZwcACv

trap と組み合わせて片づける

mktemp が引き受けるのは作るところまでで、消すのはこちらの仕事です。

そこで trap に EXIT を仕掛けておくと、正常終了でも途中終了でも同じ後片付けが走ります。

#!/usr/bin/env bash
set -euo pipefail

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

echo hello > "$tmp/out.txt"
wc -l < "$tmp/out.txt"

実際に走らせて、終了後にディレクトリが消えていることまで確認しました。

$ bash demo.sh
1
$ test -d /tmp/tmp.KT6AZwcACv || echo "片づけ済み"
片づけ済み

作る行と消す行を隣同士に書くのが、私にとって一番効いた習慣でした。

後から「どこかで消しているはず」と探し回らずに済みますし、読む人にも意図がまっすぐ伝わる。

なお、処理の出力を一時ファイルへ逃がすときのリダイレクト記法は、別記事で整理しています。

2>&1 の意味とは?標準出力・標準エラー出力とリダイレクトを整理

つまずきやすいところ

最後に挙げておきたいのは、実際に踏みやすい2点。

ひとつめは -u で、これは名前だけを返してファイルを作りません。

$ mktemp -u /tmp/demo/dry.XXXXXX
/tmp/demo/dry.GluR5h

一見便利ですが、受け取った名前を自分で作るまでの隙間に他のプロセスが割り込めるので、安全性という肝心の長所が消えてしまいます。

名前の形だけ確かめたいデバッグ用、くらいの距離感がちょうどいい。

実体が欲しいなら、素直に mktemp で作ってから中身を書き換えるほうが結局は近道です。

ふたつめは、受け取ったパスを引用符でくくり忘れる事故なのです。

mktemp が返す名前そのものに空白は入りませんが、TMPDIR に空白を含むディレクトリが設定されていれば話が変わります。

変数は必ずダブルクォートで囲む癖をつけておけば、この手のばらけ方は起きません。

最後に

mktemp が肩代わりしてくれるのは、名前を考える手間そのものではありません。

衝突しない名前を、作成と同時に確定させるという一点。

そこへ trap を1行足すだけで、散らかさないスクリプトが自然に書けるようになります。

次に中間ファイルを置きたくなったら、固定名を書く前に mktemp と打ってみてください。

以上です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA