Bashでシェルスクリプトを書い居ると特に
適当に一時ファイルにコマンド結果を書き出して後で参照したりすることがよくありますが、
今まで適当にカレントディレクトリに.tmp
とかのファイルを作ったりしてましたが、
今更ながらmktemp
というコマンドを知ったのでちょっと整理してみました。
これまでの一時ファイル
ざっとスクリプトを見たところ、今までにやったことがあるのは
- 単純に
f=/tmp/tmpfile
など、/tmp
に適当なファイルを作る。- これだと違うプロセスから同じコマンドを使うと競合してしまう。
- また、
/tmp
直下に置くことが良いのか、と言う問題。 f=/tmp/$USER/tmpfile
とすればLinuxだとOKだが、Macだと 一時ディレクトリは別にランダムな文字列とともに用意されるので駄目。f=$TMPDIR/tmpfile
と環境変数を使うこともできるが、Cygwinでは この変数は用意されてない。 (この辺下を参照)
- 単純に
f=~/tmp/tmpfile
など、~/tmp
に適当なファイルを作る。- 競合してしまうのは上と一緒。
- 自分のHomeには必ず最初に
tmp
を作ってるので大丈夫、という環境依存。- たまに、最初に
mkdir -p ~/tmp
とかしてみる。
- たまに、最初に
- 単純に
f=.tmp
など、カレントディレクトリに.
で始まるファイルを作る- 違うディレクトリから呼んだ場合は競合を回避出来る。
- 途中で終了シグナルを送られた場合の処理をきちんとしておかないと その場に一時ファイルが残ってしまう。
f=/tmp/tmpfile.$$
など、プロセスIDを付与する。- 競合を回避出来る。
ということで、上の方だと結構問題があるものもありますが、 プロセスIDなんかを振ってやれば大分ましにはなると思います。
TMP/TEMDIR
Linuxとかだと、TMPDIR
と言う環境変数が/tmp/$USER
を指して居て、
ここを自分の一時ファイル置き場として使えます。
(リブート等のタイミングでクリアされる)
また、Macだと同じTMPDIR
と言う環境変数に
/var/folders/XX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/X/
といった感じのランダムな文字列(Xの部分)で決められたディレクトリ
(この場合は最後に/
が付く)が
割り当てられていて、再起動のタイミング等で変わります。
(その代わり古いのも暫く残る。)
これらの値を使ってその配下に一時ファイルを作れば良いわけですが、 1つ問題があって、Cygwinではこの値が設定されていません。
その代わりCygwinにはTMP
及びTEMP
と言う値が/tmp
として設定されています。
これが結構面倒なので、.bashrc
なんかでTMPDIRが無いときに
値を設定する様、次の様な設定を書いておくと便利かと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
elif [ -w /tmp/$USER ];then
以下はちょっと冗長かもしれませんが、
念のため。
mktemp
こちらを見て知りました。
安全な一時ファイルの作成と削除の方法 - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog
結構当たり前なコマンドな感じなのに今まで人が作ったスクリプトで一回も見たことが無かった気がする。。。
適当なTMPディレクトリにランダムな文字列を使った一時ファイル(ディレクトリ)を 作ってその名前を返してくれるコマンドです。
上の様にTMPDIR
を指定して、さらにプロセスIDなんかをファイル名に
与えてあげればほとんどの場合は回避出来ますが、
mktempはさらに自動的に700
へパーミッションを指定することと、
シンボリックリンク攻撃の脆弱性を回避出来る、ということもあるみたいなので、
せっかく便利なコマンドですし、使っていこうと思います。
ただ、少し注意が必要なのが、GNUとBSDのコマンドで引数の定義が違います。
GNUだと、何も引数なしで実行すると、$TMPDIR/tmp.XXXXXXXX
1
と言ったファイルが出来るのですが、BSDの場合は引数なしだと
エラーになります。
追記: 2015/10/19
どうもOS X内のmktemp
コマンドにアップデートがあったらしく、
10.11 El Capitanでは
引数無しでもエラー無しに-t tmp
を与えたのと同様の結果が得られます。
追記ここまで
同様のファイルを作るにはprefixを-t
で与えて
$ mktemp -t tmp # BSD mktemp
/var/folders/XX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/X/tmp.XXXXXXXX
とします。ただ、ここでさらにやっかいなのが、
GNUの場合は-t
を付けると、TMPDIR
の中にその名前その物が作られます。
$ mktemp -t tmp # GNU mktemp
/tmp/user/tmp
従ってこれだと競合が回避出来ません。 (mktmepの場合だとファイルが存在するとエラーを返すのでここで止まります)
そこで、両方で使えるような移植性を実現するためにはこんな感じでmktemp
を呼びます。
1
|
|
最初にGNUだと仮定して、エラーになった場合にBSDだと思って-t
でprefixを指定して
一時ファイルを作成します。
もしくはTMPDIR
が正しく設定してあると仮定すれば
1
|
|
の様に、直接ファイル名を与えてあげると両方共同じ様に働くので
綺麗には出来ますし、tmp
の部分も自由に替えられます。
この場合に、最後にX
を付けると、その部分はX
の数だけランダムな文字が
付け加えられます。(競合を避けるために複数個のX
が必須。)
もしくはTMPDIR
の問題を考えると、mktemp
を使う場合には単純に/tmp
ディレクトリのが良いかもしれません。
途中でスクリプトを止めた時の一時ファイルの処理
一時ファイルをスクリプトの最後で消すような処理をしている場合、
C-c
等でスクリプトを止めた時にはそのままファイルが残ってしまいます。
これを回避するためには上で挙げたサイトにもありますが、
1 2 3 4 5 6 7 8 9 10 |
|
追記: 2015/11/19
trap
の部分が以前はダブルクォートで囲ってましたが、
ダブルクォートしてしまうと、これを書いた時点で変数が評価されてしまいます
つまり最初にtmpfile
の値が入ってないので削除が行われません。
この場合はシングルクォートが正しく、これだと実際に trapされた時に評価されます。
また$tmpfile
の部分も以前はクォートされてませんでしたが、
[[ ]]
の中身がなにもないとbash: syntax error near `]]’
なエラーが出ます。
ここでは中身が空白で無かったら、と言う事で使いたいので、 ダブルクォートで囲んで使います。
追記ここまで
この様にtrapを使って何らかの要因で止められた時に ファイルを削除する様にしておきます。
上でtrapに与えてる数字は送られるシグナルの番号でそれぞれ
- 1 HUP: ハングアップ(sshで接続が切れる、等)
- 2 INT: C-cやDelが押された時の割り込みシグナル
- 3 QUIT: クイットシグナル
- 15 TERM:
kill
コマンドがデフォルトで送るシグナル
の時(これらの名前を与えてもOK)。 通常送られるのは上の4つくらいなのでこれで十分です。
kill
による強制終了(kill kill
またはkill -9
)で送られる
9(KILL)はtrap
では捉えられません。
0(EXIT)は終了時に必ず送られるシグナルで、 上に挙げたシグナル(KILL以外)が送られても、その後にEXITが行われるので、
trap "echo exit" 0 1 2 3 15
みたいな設定をしておくと、C-c
等で止めるとexit
が2回出ます。
$tmpfile
を必ず終了時に消して、特にチェックも必要なければ
trap "[[ $tmpfile ]] && rm -f $tmpfile" 0
と上のスクリプトを置き換えて、最後のrm
文を消すことも出来ます。
-
Xはランダムな文字列、TMPDIRが指定されてない場合は
/tmp
が使われます。 従ってCygwinの場合には丁度?TMP
/TEMP
と同じディレクトリを指す事になります。 ↩