mktemp 入門 - 安全な一時ファイル・ディレクトリを作る

mktemp 入門 - 安全な一時ファイル・ディレクトリを作る

この記事で解決できること

  • mktemp一時ファイル / ディレクトリを安全に作る 型が分かる
  • file.$$ や固定名の一時ファイルが なぜ危険か を理解できる
  • trap と組み合わせて 後始末まで含めた定石 が書ける

結論(実務の型)

  • 一時ファイル → tmp=$(mktemp)
  • 一時ディレクトリ → dir=$(mktemp -d)
  • 作ったら必ず trap 'rm -rf "$dir"' EXIT自動削除

前提(対象環境)

  • GNU coreutils(mktemp --version で確認、本記事は 9.4 で検証)
  • 一般的な Linux ディストリビューション(Ubuntu / RHEL 系など)
  • BSD / macOS の mktemp はオプション体系が異なる

mktemp とは何か?

結論: mktemp は一意な名前の一時ファイル・ディレクトリを安全に作成し、そのパスを標準出力に返す coreutils コマンド。

mktemp は名前が衝突しない一時ファイルを 作成しつつ、そのパスを出力する。アプリケーションが自前でファイル名を組み立てる必要がなくなる。

$ mktemp
/tmp/tmp.A1b2C3d4E5

テンプレートを省略すると $TMPDIR(未設定なら /tmp)に tmp.XXXXXXXXXX という名前で作られる。X の部分がランダムな文字に置換される。

man の記述(GNU coreutils 9.4):

Create a temporary file or directory, safely, and print its name.
Files are created u+rw, and directories u+rwx, minus umask restrictions.

ポイントは 「safely」「print its name」 の 2 点。作成と命名がアトミックに行われ、所有者だけが読み書きできる権限が付く。

なぜ mktemp を使うのか?

結論: 固定名や $$(PID)由来の名前は予測可能で、競合・上書き・シンボリックリンク攻撃の温床になる。mktemp はこれを原理的に防ぐ。

やりがちな危険パターン

# NG: 固定名
tmpfile=/tmp/myapp.tmp

# NG: PID は予測されやすく、同時実行で衝突する
tmpfile=/tmp/myapp.$$

これらの問題点:

  • 競合(race condition): 同じスクリプトを複数同時に走らせると名前が衝突し、片方のデータを壊す
  • シンボリックリンク攻撃: 攻撃者が予測した名前で /tmp に事前にシンボリックリンクを張っておくと、スクリプトが意図しないファイルを書き換えてしまう
  • 権限の取りこぼし: touch> での作成は umask 次第で他人に読まれうる

mktemp が解決すること

$ tmpfile=$(mktemp)
$ stat -c '%a %n' "$tmpfile"
600 /tmp/tmp.A1b2C3d4E5

mktemp作成と命名を一度の不可分な操作 で行い、既存ファイルがあれば失敗する。ファイルは u+rw(umask 適用後で通常 600)、ディレクトリは u+rwx(通常 700)で作られるため、最初から所有者専用になる。

予測可能な名前を使う限り、後から chmod しても競合の窓は塞げない。作成の瞬間に安全であること が重要。

一時ファイル・ディレクトリを作るには?

結論: ファイルは mktemp、ディレクトリは mktemp -d。返ってきたパスを変数に取り、以降はその変数を使う。

一時ファイル

$ tmpfile=$(mktemp)
$ echo "data" > "$tmpfile"
$ cat "$tmpfile"
data

一時ディレクトリ

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

-d--directory)でファイルではなくディレクトリを作る。複数の中間ファイルをまとめて置きたいときに使い、後始末は rm -rf "$tmpdir" 一発で済む。

変数に取るときは必ず "$tmpfile" のように ダブルクォート すること。/tmp 以外の TMPDIR にスペースを含むパスが入っていても壊れない。

作成場所や名前を細かく指定するには?

結論: テンプレートで名前の形を、-p で作成先ディレクトリを、--suffix で拡張子を制御できる。テンプレートには末尾に 3 個以上の連続した X が必要。

テンプレートを渡す

$ mktemp myapp.XXXXXX
myapp.k3Df9a

X がランダム文字に置換される。最後の構成要素に連続した X が 3 個以上 必要というのが GNU の仕様。

作成先を指定する(-p / --tmpdir)

$ mktemp -p /var/tmp myapp.XXXXXX
/var/tmp/myapp.q7Zb2K

-p DIR--tmpdir[=DIR])でベースディレクトリを指定する。/tmp は再起動で消えることがあるため、長めに残したい一時データは /var/tmp を選ぶといった使い分けに使う。

拡張子を付ける(--suffix)

$ mktemp --suffix=.log myapp.XXXXXX
myapp.a8Kd2p.log

ツールが拡張子で形式を判定する場合に便利。SUFF にスラッシュは使えない。

テンプレートと -p / -t の関係(補足)
  • -p DIR 指定時、テンプレートは 絶対パスにできない。スラッシュは含められるが、mktemp が作るのは最終構成要素のみ。
  • -t は「テンプレートを単一のファイル名要素として $TMPDIR 等の下に作る」古いオプションで、現在は deprecated。新規スクリプトでは -p を使う。

スクリプトで確実に後始末するには?

結論: trap 'rm -rf "$tmpdir"' EXIT を作成直後に仕掛ければ、正常終了でもエラー終了でも一時ファイルが残らない。

一時ファイルを作るスクリプトの定石:

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

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

# ここで $tmpdir を自由に使う
curl -s https://example.com/data.json > "$tmpdir/data.json"
jq '.items' "$tmpdir/data.json"

# スクリプト終了時、trap が自動で $tmpdir を削除する

ポイント:

  • mktemp -d直後trap を仕掛ける(作成と後始末をセットで書く)
  • EXIT を捕捉するので、set -e による途中終了でもクリーンアップが走る
  • ディレクトリごと作っておけば、中に何ファイル増えても rm -rf 一発

詳しくは trap でシグナルと後始末を扱う を参照。

trap の対象を $tmpdir(mktemp が返した一意なパス)に限定すること。/tmp/* のような広いパターンを rm -rf するスクリプトは事故の元。

知っておきたいオプションと落とし穴

結論: -u(dry-run)は名前だけ返して作成しないため安全性が崩れる。基本は作成までさせる素の mktemp を使う。

オプション 意味 注意
-d ファイルでなくディレクトリを作る 後始末は rm -rf
-u 名前を表示するだけで作成しない(dry-run) 競合の窓が開く。原則使わない
-q 作成失敗時の診断メッセージを抑制 スクリプトで自前にエラー処理する場合
-p DIR 作成先ディレクトリを指定 テンプレートは絶対パス不可
--suffix=SUFF 末尾に拡張子等を付与 スラッシュ不可

返り値の取り違えにも注意:

# NG: 作成に失敗してもそのまま進むと空文字を rm しかねない
tmpfile=$(mktemp)

# OK: 失敗を検知して止める
tmpfile=$(mktemp) || exit 1

まとめ / 次に読む