拡張 POSIX シェルスクリプト Advent Calendar 2013、6日目の記事です。
本日のお題は「一時ファイルの作成と削除」ですが、 よくあるシェルスクリプトのダメ出しネタになってしまいました。
安全な一時ファイルの作成方法
たとえば、こんな感じのありがち(?)なスクリプトがあったとします。 意味のない処理内容ですが、雰囲気だけ察してください。
#!/bin/bash
tmpfile=/tmp/words.tmp
rm -f $tmpfile
echo 'しにたい' >>$tmpfile
echo 'とりあえずねよう' >>$tmpfile
sleep 3
echo -n '今どんな気持ち? '
read feeling
echo $feeling >>$tmpfile
cat $tmpfile
rm $tmpfile
全然駄目ですね。 もし「何にも問題ないじゃないか!」という感想をお持ちの方は、 ぜひ悔い改めてください。
肝心の「一時ファイルの作成」だけでも、次の問題があります。
- このスクリプトを同時に起動したり、 同じパスのファイルを利用するほかのプロセスが同時に走ると競合が起きて、 ファイルが壊れる可能性がある。
- ファイルの中身を誰でも参照できる可能性がある。
- シンボリックリンク攻撃の脆弱性がある。
競合の問題は運用で回避すれば発生しないので、それでよければ無視できます。(酷い)
ファイル内容漏洩の問題は、この例のように機密性のないどうでもいい内容であったり、 悪意ある者がローカルユーザーなどの参照する手段・権限を持たない、 かつほかの脆弱性を突かれてそれを奪取されたときのリスクを考慮しなくてよいのであれば、 無視できます。(これも酷い)
シンボリックリンク攻撃の問題も、ファイル内容漏洩と同様です。 (一時ファイルの参照ではなく、同名のシンボリックリンクを作れるかどうかの問題)
これらの問題の対処方法ですが、素人は黙って mktemp
(1) を使ってください。
それだけで競合も内容漏洩もシンボリックリンク攻撃も避け、
安全にファイルを作成することができます。
#!/bin/bash
tmpfile=$(mktemp)
echo 'しにたい' >>$tmpfile
echo 'とりあえずねよう' >>$tmpfile
sleep 3
echo -n '今どんな気持ち? '
read feeling
echo $feeling >>$tmpfile
cat $tmpfile
rm $tmpfile
mktemp
は次のような仕様でファイルを作成します。
- 既存のファイルを絶対に上書きしない。
- 競合の回避。
- シンボリックリンク攻撃の回避。
- 作成されるファイルのモードは必ず 0700 になる。
- ファイル内容漏洩の回避。
よくありがちなシェルスクリプトのダメ出し
一時ファイルの作成は問題なくなりましたが、まだ駄目な点があります。
- 各コマンドが失敗することを考慮していない。滅多に発生しないから無視ですか?
mktemp
が作成する一時ファイル名は/tmp/tmp.<ランダム文字列>
と謎めいたものになり、何者が由来かわかりにくい。 トラブルシュート時などに厄介。$feeling
に空白文字や*
などパス名展開のパターン文字が入っていたらどうなるでしょうか? (zsh ならクォートなし変数展開後のワード分割もパス名展開もデフォルトではしないので問題なし)read feeling
にも問題あるのだけど、これはまた別の機会に。
さらに修正してやります。
#!/bin/bash
# or
#!/bin/ksh
#!/bin/zsh
if [[ -n ${ZSH_VERSION-} ]]; then
emulate -R ksh
set -o BSD_ECHO
fi
set -e
tmpfile=$(mktemp "/tmp/${0##*/}.tmp.XXXXXX")
echo 'しにたい' >>"$tmpfile"
echo 'とりあえずねよう' >>"$tmpfile"
sleep 3
echo -n '今どんな気持ち? '
IFS= read -r feeling
echo "$feeling" >>"$tmpfile"
cat "$tmpfile"
rm "$tmpfile"
簡単に解説します。
- コマンド失敗時にそれを検知して即終了するために
set -e
を追加。 (この仕様でよいかは議論の余地あり) - 一時ファイル名の識別がしやすいように
mktemp
に指定する一時ファイル名のテンプレートにスクリプト名を含める。 $feeling
を一時ファイルに出力するときにワード分割とパス名展開がされないようにダブルクォートで括った。 コーディングスタイルを統一するため、ほかの変数展開もダブルクォート括りに。read
の問題も修正。こいつの解説はまた後日。- ついでに zsh 対応も入れておきました。 ksh はそのままで大丈夫。
これで概ね問題のないシェルスクリプトになりました。
安全な一時ファイルの削除方法
残る問題が一つあります。
- 最後の
rm
の前に終了したり殺されたときに一時ファイルが残ってしまう。
これは先日紹介した方法でスクリプト終了イベントとシグナルを捕捉 すればいいですね。
#!/bin/bash
# or
#!/bin/ksh
#!/bin/zsh
if [[ -n $ZSH_VERSION ]]; then
emulate -R ksh
set -o BSD_ECHO
fi
set -e
unset tmpfile
atexit() {
[[ -n ${tmpfile-} ]] && rm -f "$tmpfile"
}
trap atexit EXIT
trap 'rc=$?; trap - EXIT; atexit; exit $?' INT PIPE TERM
tmpfile=$(mktemp "/tmp/${0##*/}.tmp.XXXXXX")
echo 'しにたい' >>"$tmpfile"
echo 'とりあえずねよう' >>"$tmpfile"
sleep 3
echo -n '今どんな気持ち? '
IFS= read -r feeling
echo "$feeling" >>"$tmpfile"
cat "$tmpfile"
これで完成です。
AIX など mktemp
コマンドがない環境の場合は、
別途用意することをお勧めします。
ちなみに mktemp
相当の機能はシェルだけでも実装可能です。
もし機会があれば、実装の紹介とともに、
どのようにして安全に一時ファイルを作成すればよいかを解説したいと思います。
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013