予期しないbash出口で作成された一時ファイルを削除する


89

bashスクリプトから一時ファイルを作成しています。処理の最後にそれらを削除していますが、スクリプトはかなり長時間実行されているため、実行中にスクリプトを強制終了するか、CTRL-Cキーを押すだけで、一時ファイルは削除されません。
これらのイベントをキャッチして、実行が終了する前にファイルをクリーンアップする方法はありますか?

また、これらの一時ファイルの名前と場所には、何らかのベストプラクティスがありますか?
私は現在、どちらを使用するかわかりません:

TMP1=`mktemp -p /tmp`
TMP2=`mktemp -p /tmp`
...

そして

TMP1=/tmp/`basename $0`1.$$
TMP2=/tmp/`basename $0`2.$$
...

それとももっと良い解決策がありますか?


回答:


97

トラップ」を設定して終了時に実行するか、control-cで実行してクリーンアップできます。

trap "{ rm -f $LOCKFILE; }" EXIT

あるいは、私のお気に入りのunix-ismsの1つは、ファイルを開いてから、ファイルを開いたまま削除します。ファイルはファイルシステムに残り、読み取りと書き込みはできますが、プログラムが終了するとすぐにファイルは消えます。ただし、bashでそれをどのように行うかはわかりません。

ところで、独自のソリューションを使用する代わりに、mktempを使用することをお勧めします。ユーザーがプログラムが巨大な一時ファイルを作成することを予想している場合、TMPDIR/ var / tmpなどの大きな場所に設定したいと思うかもしれません。mktempはそれを認識しますが、手巻きソリューション(2番目のオプション)は認識しません。TMPDIR=/var/tmp gvim -d foo barたとえば、私は頻繁に使用します。


8
バッシュ、とのexec 5<>$TMPFILE読み書きなど$ TMPFILEとの結びつきファイルディスクリプタ5、あなたが使用することができ<&5>&5および/proc/$$/fd/5それ以降(Linuxの)。唯一の問題は、バッシュがseek機能を欠いていることです...
ephemient

あなたが提供したリンクは私が必要とするものを最もよく説明するものだからです。ありがとう
skinp 2009年

4
いくつかの注意事項trap:トラップする方法はありませんSIGKILL(設計により、実行が直ちに終了するため)。したがって、それが発生する可能性がある場合は、フォールバック計画(などtmpreaper)を用意してください。第2に、トラップは累積的ではありません。実行するアクションが複数ある場合は、それらすべてをtrapコマンドに含める必要があります。複数のクリーンアップアクションに対処する1つの方法は、関数を定義し(必要に応じて、プログラムの進行に合わせて再定義できます)、それを参照することですtrap cleanup_function EXIT
Toby Speight

1
使用しtrap "rm -f $LOCKFILE" EXITないと、予期しないファイルの終わりエラーが発生します。
Jaakko

3
Shellcheckは、トラップが呼び出されたときに式が後でではなく二重引用符で「今」展開されることを警告して、単一引用符を使用するようにしました。
LaFayette 2018

110

通常は、すべての一時ファイルを配置するディレクトリを作成し、その直後に、スクリプトが終了したときにこのディレクトリをクリーンアップするEXITハンドラーを作成します。

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

すべての一時ファイルをの下に置くと$MYTMPDIR、ほとんどの状況でスクリプトが終了すると、すべての一時ファイルが削除されます。ただし、SIGKILL(kill -9)を使用してプロセスを強制終了すると、すぐにプロセスが強制終了されるため、その場合、EXITハンドラーは実行されません。


27
+1間違いなくTERM / INT / HUP /他に考えられるものではなく、EXITでトラップを使用してください。ただし、パラメータ展開を引用することを忘れないでください。トラップを一重引用することお勧めします
。trap

7
単一引用符。これは、後でスクリプト内で、状況が原因でクリーンアップしてTMPDIRを変更することにした場合でもトラップが機能するためです。
lhunath 2009年

1
@AaronDigullaなぜ$()とバッククォートが重要なのですか?
Ogre Psalm33


3
@AlexanderTorstlingコードは、注入によって任意のコードが実行されるのを防ぐために、常に単一引用符で囲む必要があります。データをbashコードSTRINGに展開すると、そのデータはコードが実行できるすべてのコードを実行できるようになり、空白スペースに関する無害なバグだけでなく、奇妙な理由でホームディレクトリをクリアしたり、セキュリティホールを導入するなどの破壊的なバグも発生します。trapはbashコードの文字列を受け取り、それは後で評価されることに注意してください。したがって、後でトラップが発動すると、単一引用符はなくなり、構文上の二重引用符だけが存在します。
lhunath

25

trapコマンドを使用して、CTRL-Cのようなスクリプトまたはシグナルの終了を処理します。詳細については、GregのWikiを参照してください。

basename $0一時ファイルについては、使用することをお勧めします。また、十分な一時ファイル用のスペースを提供するテンプレートを提供することもできます。

tempfile() {
    tempprefix=$(basename "$0")
    mktemp /tmp/${tempprefix}.XXXXXX
}

TMP1=$(tempfile)
TMP2=$(tempfile)

trap 'rm -f $TMP1 $TMP2' EXIT

1
TERM / INTでトラップしません。EXITのトラップ。受信した信号に基づいて終了条件を予測しようとするのは、愚かで間違いなくキャッチオールではありません。
lhunath 2009年

3
マイナーポイント:単一のバッククォートの代わりに$()を使用します。また、スペースが含まれる可能性があるため、$ 0を二重引用符で囲みます。
アーロンディグラ2009年

さて、このコメントではバッククォートは問題なく機能しますが、それは公平な点$()です。二重引用符も追加しました。
ブライアンキャンベル

1
サブルーチン全体をTMP1 = $(tempfile -s "XXXXXX")に置き換えることができます
Ruslan Kabalin

4
@RuslanKabalinすべてのシステムにtempfileコマンドがあるわけではありませんが、私が知っているすべての合理的な最新システムにはmktempコマンドがあります。
ブライアンキャンベル

9

選ばれた答えはbashismであり、これは

trap "{ rm -f $LOCKFILE }" EXIT

bashでのみ機能します(shellがdashまたはclassicの場合はCtrl + cをキャッチしませんsh)。ただし、互換性が必要な場合は、トラップするすべての信号を列挙する必要があります。

また、スクリプトがシグナル "0"(別名EXIT)のトラップを終了すると、常に実行され、trapコマンドが二重に実行されることに注意してください。

EXIT信号がある場合、すべての信号を1行にスタックしない理由。

よりよく理解するには、変更なしでさまざまなシステムで動作する次のスクリプトを見てください。

#!/bin/sh

on_exit() {
  echo 'Cleaning up...(remove tmp files, etc)'
}

on_preExit() {
  echo
  echo 'Exiting...' # Runs just before actual exit,
                    # shell will execute EXIT(0) after finishing this function
                    # that we hook also in on_exit function
  exit 2
}


trap on_exit EXIT                           # EXIT = 0
trap on_preExit HUP INT QUIT TERM STOP PWR  # 1 2 3 15 30


sleep 3 # some actual code...

exit 

このソリューションでは、実際のシグナルの発生時にコードの一部を最終終了の直前にpreExit実行できるため(関数)、必要に応じて実際のEXITシグナル(終了の最終段階)でコードを実行できます。


4

$$で予測可能なファイル名を使用する代わりに、セキュリティホールが広がっており、これを使用することを決して考えるべきではありません。シングルユーザーPC上の単純な個人用スクリプトであっても。それはあなたが得るべきではない非常に悪い習慣です。BugTraqは「安全でない一時ファイル」のインシデントでいっぱいです。一時ファイルのセキュリティ面の詳細についてこちらこちらこちらをご覧ください。

私は当初、安全でないTMP1とTMP2の割り当てを引用することを考えていましたが、次の考えでは、それはおそらく良い考えではないでしょう。


できれば、次のようにします。セキュリティアドバイス用に+1、悪いアイデアと参照を引用しないために別の+1
TMG

1

tempfileは/ tmpに安全な方法でファイルを作成するを使用することを好み、その名前について心配する必要はありません。

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit

tempfileは安全ですが、残念ながら非常に移植性が低いため、回避するか、少なくともエミュレートすることをお勧めします。
lericson 2013年

1

多くの人がファイル名にスペースが含まれないと思っているとは信じられません。$ TMPDIRが「一時ディレクトリ」に割り当てられると、世界がクラッシュします。

zTemp=$(mktemp --tmpdir "$(basename "$0")-XXX.ps")
trap "rm -f ${zTemp@Q}" EXIT

スペースや、ファイル名に含まれる一重引用符やキャリッジリターンなどのその他の特殊文字は、適切なプログラミングの習慣として、コードで考慮する必要があります。


+1一重引用符trap 'rm -f "${zTemp}"' EXITはスペースやその他の特殊文字を正しく処理しますが、この回答の解決策はの評価を遅らせませんzTemp。したがってzTemp、スクリプトの後半で変更されることの価値について心配する必要はありません。また、zTemp関数に対してローカルに宣言できます。グローバルスクリプト変数である必要はありません。
ロビンA.ミード

割り当てのRHSを囲む二重引用符は不要です。
ロビンA.ミード

なお、${parameter@operator}拡張はBash 4.4(2016年9月リリース)で追加されました。
ロビンA.ミード

-4

mktempで作成したtmpファイルを削除する必要はありません。とにかく後で削除されます。

可能な場合はmktempを使用して、 '$$'プレフィックスよりも多くの一意のファイルを生成します。そしてそれは一時ファイルを作成して明示的にそれらを/ tmpに置くためのよりクロスプラットフォームの方法のように見えます。


4
誰または何によって削除されましたか?
innaM 2009年

一定期間後に操作|ファイルシステム自体によって削除
Mykola Golubyev 2009年

4
マジック?cronjob?または再起動したSolarisマシン?
innaM 2009年

おそらくそのうちの一つ。一時的なファイルが何らかの中断によって削除されなかった場合(あまり頻繁ではありません)、いつかtmpファイルが削除されます。そのため、一時ファイルはtempと呼ばれていました。
Mykola Golubyev 2009年

22
/ tmpに入れられた何かがそこに永遠に残ると想定してはいけません。同時に、それが魔法のようになくなると思い込まないでください。
innaM 2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.