Menghapus file temp yang dibuat saat keluar dari bash yang tidak terduga

89

Saya membuat file sementara dari skrip bash. Saya menghapusnya di akhir pemrosesan, tetapi karena skrip berjalan cukup lama, jika saya mematikannya atau hanya CTRL-C selama menjalankan, file temp tidak akan dihapus.
Apakah ada cara untuk menangkap kejadian tersebut dan membersihkan file sebelum eksekusi berakhir?

Selain itu, apakah ada praktik terbaik untuk penamaan dan lokasi file temporer tersebut?
Saat ini saya tidak yakin antara menggunakan:

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

dan

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

Atau mungkin adakah solusi yang lebih baik?

skinp
sumber

Jawaban:

98

You could set a "trap" to execute on exit or on a control-c to clean up.

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

Alternatively, one of my favourite unix-isms is to open a file, and then delete it while you still have it open. The file stays on the file system and you can read and write it, but as soon as your program exits, the file goes away. Not sure how you'd do that in bash, though.

BTW: One argument I'll give in favour of mktemp instead of using your own solution: if the user anticipates your program is going to create huge temporary files, he might want set TMPDIR to somewhere bigger, like /var/tmp. mktemp recognizes that, your hand-rolled solution (second option) doesn't. I frequently use TMPDIR=/var/tmp gvim -d foo bar, for instance.

Paul Tomblin
sumber
8
Dengan Bash, exec 5<>$TMPFILEkaitkan deskriptor file 5 ke $ TMPFILE sebagai baca-tulis, dan Anda dapat menggunakan<&5, >&5, and /proc/$$/fd/5 (Linux) thereafter. The only problem is that Bash lacks seek function...
ephemient
Accepted you answer since the link you provided is what explains the best what I needed. Thanks
skinp
4
Beberapa catatan tentang trap: there's no way to trap SIGKILL (by design, as it immediately terminates execution). So, if that might happen, have a fallback plan (such as tmpreaper). Secondly, traps are not cumulative - if you have more than one action to perform, they must all be in the trap command. One way to cope with multiple cleanup actions is to define a function (and you can redefine it as your program proceeds, if necessary) and reference that: trap cleanup_function EXIT.
Toby Speight
1
Saya harus menggunakan trap "rm -f $LOCKFILE" EXIT or I would get a unexpected end of file error.
Jaakko
3
Shellcheck memberi peringatan untuk menggunakan tanda kutip tunggal, bahwa ekspresi akan diperluas 'sekarang' dengan tanda kutip ganda daripada nanti ketika jebakan dipanggil.
LaFayette
110

Saya biasanya membuat direktori untuk menempatkan semua file sementara saya, dan kemudian segera setelah itu, buat penangan EXIT untuk membersihkan direktori ini ketika skrip keluar.

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

Jika Anda meletakkan semua file sementara Anda di bawah $MYTMPDIR, maka semuanya akan dihapus ketika skrip Anda keluar dalam banyak situasi. Membunuh proses dengan SIGKILL (kill -9) membunuh proses itu segera, jadi penangan EXIT Anda tidak akan berjalan dalam kasus itu.

Chris AtLee
sumber
27
+1 Pasti menggunakan jebakan di EXIT, bukan TERM / INT / HUP / apa pun yang dapat Anda pikirkan. Namun, ingatlah untuk mengutip ekspansi parameter Anda dan saya juga akan merekomendasikan Anda mengutip tunggal perangkap Anda: trap 'rm -rf "$ TMPDIR"' EXIT
lhunath
7
Tanda kutip tunggal, karena jebakan Anda akan tetap berfungsi jika nantinya dalam skrip Anda memutuskan untuk membersihkan dan mengubah TMPDIR karena keadaan.
lhunath
1
@AaronDigulla Mengapa $ () vs backticks penting?
Ogre Mazmur33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Aaron Digulla
3
Kode @AlexanderTorstling harus selalu dikutip tunggal untuk mencegah injeksi yang mengakibatkan eksekusi kode arbitrer. Jika Anda memperluas data menjadi kode bash STRING maka data tersebut sekarang dapat melakukan apa pun yang dilakukan kode yang menghasilkan bug yang tidak berbahaya, tetapi juga bug yang merusak seperti membersihkan homedir Anda karena alasan yang aneh atau menimbulkan lubang keamanan. Perhatikan bahwa perangkap mengambil serangkaian kode bash yang akan dievaluasi sebagaimana adanya nanti. Jadi nanti saat jebakan diaktifkan, tanda kutip tunggal akan hilang dan hanya akan ada tanda kutip ganda sintaksis.
lhunath
25

Anda ingin menggunakan perintah trap untuk menangani keluar dari skrip atau sinyal seperti CTRL-C. Lihat Greg's Wiki untuk detailnya.

Untuk file temporer Anda, gunakan basename $0 file temporer adalah ide yang bagus, serta menyediakan template yang menyediakan ruang untuk file temp yang cukup:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Brian Campbell
sumber
1
Jangan terjebak pada TERM / INT. Perangkap saat KELUAR. Mencoba untuk memprediksi kondisi keluar berdasarkan sinyal yang diterima adalah konyol dan jelas bukan tujuan akhir.
lhunath
3
Poin kecil: Gunakan $ () sebagai ganti satu backticks. Dan beri tanda kutip ganda sekitar $ 0 karena bisa berisi spasi.
Aaron Digulla
Well, backticks berfungsi dengan baik dalam komentar ini, tapi itu poin yang adil, itu baik untuk membiasakan diri menggunakan $(). Menambahkan tanda kutip ganda juga.
Brian Campbell
1
Anda dapat mengganti seluruh subrutin Anda hanya dengan TMP1 = $ (tempfile -s "XXXXXX")
Ruslan Kabalin
4
@RuslanKabalin Tidak semua sistem memiliki tempfileperintah, sementara semua sistem modern yang wajar yang saya tahu memiliki mktempperintah.
Brian Campbell
9

Perlu diingat bahwa jawaban yang dipilih adalah bashism, yang artinya solusi sebagai

trap "{ rm -f $LOCKFILE }" EXIT

akan bekerja hanya di bash (tidak akan menangkap Ctrl + c jika shell ada dash atau klasiksh ), tetapi jika Anda menginginkan kompatibilitas maka Anda masih perlu menghitung semua sinyal yang ingin Anda jebakan.

Juga perlu diingat bahwa ketika skrip keluar dari perangkap untuk sinyal "0" (alias KELUAR) selalu dilakukan yang mengakibatkan eksekusi ganda trap perintah.

Itulah alasan untuk tidak menumpuk semua sinyal dalam satu baris jika ada sinyal EXIT.

Untuk lebih memahaminya, lihat skrip berikut yang akan berfungsi di berbagai sistem tanpa perubahan:

#!/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 

Solusi ini akan memberi Anda lebih banyak kontrol karena Anda dapat menjalankan beberapa kode Anda saat terjadi sinyal aktual sebelum keluar akhir ( preExitfungsi) dan jika diperlukan Anda dapat menjalankan beberapa kode pada sinyal KELUAR aktual (tahap akhir keluar)

Alex
sumber
4

Alternatif menggunakan nama file yang dapat diprediksi dengan $$ adalah celah keamanan yang menganga dan Anda tidak boleh pernah berpikir untuk menggunakannya. Bahkan jika itu hanya skrip pribadi sederhana di PC pengguna tunggal Anda. Ini adalah kebiasaan yang sangat buruk yang tidak boleh Anda dapatkan. BugTraq penuh dengan insiden "file temp tidak aman". Lihat di sini , di sini dan di sini untuk informasi lebih lanjut tentang aspek keamanan file temp.

Saya awalnya berpikir untuk mengutip tugas TMP1 dan TMP2 yang tidak aman, tetapi setelah dipikir-pikir, itu mungkin bukan ide yang baik .

hlovdal.dll
sumber
Saya akan memberikan jika saya bisa: +1 untuk saran keamanan dan +1 lainnya untuk tidak mengutip ide buruk dan referensi
TMG
1

Saya lebih suka menggunakan tempfileyang membuat file di / tmp dengan cara yang aman dan Anda tidak perlu khawatir tentang penamaannya:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Ruslan Kabalin
sumber
sayangnya, tempfile sangat tidak bisa dibawa-bawa meskipun lebih aman, jadi lebih baik menghindarinya atau setidaknya menirunya.
lericson
1

Saya tidak percaya bahwa begitu banyak orang menganggap bahwa nama file tidak akan mengandung spasi. Dunia akan macet jika $ TMPDIR pernah ditetapkan ke "direktori sementara".

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

Spasi dan karakter khusus lainnya seperti tanda kutip tunggal dan tanda kutip dalam nama file harus dipertimbangkan dalam kode sebagai persyaratan kebiasaan pemrograman yang baik.

Paul
sumber
Perlu dicatat bahwa ${parameter@operator}ekspansi ditambahkan di Bash 4.4 (dirilis September 2016).
Robin A. Meade
Setara dengan Bash <v4.4:trap "rm -f $(printf %q "$zTemp")" EXIT
Robin A. Meade
Ini adalah jawaban terbaik karena tidak perlu khawatir tentang nilai yang zTempakan diubah nanti dalam skrip. Juga, zTempdapat dideklarasikan localke suatu fungsi; itu tidak perlu variabel skrip global.
Robin A. Meade
-4

Anda tidak perlu repot menghapus file tmp yang dibuat dengan mktemp. Mereka akan tetap dihapus nanti.

Gunakan mktemp jika Anda bisa karena ini menghasilkan lebih banyak file unik daripada awalan '$$'. Dan sepertinya lebih banyak cara lintas platform untuk membuat file temp lalu secara eksplisit memasukkannya ke / tmp.

Mykola Golubyev
sumber
4
Dihapus oleh siapa atau apa?
innaM
Dihapus oleh operasi | sistem file itu sendiri setelah beberapa periode waktu
Mykola Golubyev
4
Sihir? Cronjob? Atau mesin Solaris yang di-boot ulang?
innaM
Mungkin salah satunya. Jika file temp tidak dihapus oleh beberapa gangguan (tidak akan terlalu sering) suatu hari nanti file tmp akan dihapus - itulah mengapa disebut temp.
Mykola Golubyev
22
Anda tidak boleh, tidak boleh, tidak boleh berasumsi bahwa sesuatu yang dimasukkan / tmp akan tetap ada di sana selamanya; pada saat yang sama, Anda tidak boleh berasumsi bahwa ini akan hilang secara ajaib.
innaM