【问题标题】:Removing created temp files in unexpected bash exit在意外的 bash 退出中删除创建的临时文件
【发布时间】:2010-10-15 18:42:59
【问题描述】:

我正在从 bash 脚本创建临时文件。我在处理结束时删除它们,但是由于脚本运行了很长时间,如果我在运行期间将其杀死或只是 CTRL-C,则不会删除临时文件。
有没有办法在执行结束之前捕获这些事件并清理文件?

另外,这些临时文件的命名和位置是否有某种最佳实践?
我目前不确定是否使用:

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

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

或者有没有更好的解决方案?

【问题讨论】:

标签: bash exit temporary-files


【解决方案1】:

我通常创建一个目录来放置我的所有临时文件,然后立即创建一个 EXIT 处理程序以在脚本退出时清理此目录。

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

如果您将所有临时文件放在$MYTMPDIR 下,那么在大多数情况下,当您的脚本退出时,它们都会被删除。使用 SIGKILL (kill -9) 终止进程会立即终止该进程,因此您的 EXIT 处理程序不会在这种情况下运行。

【讨论】:

  • +1 一定要在 EXIT 上使用陷阱,而不是愚蠢的 TERM/INT/HUP/你能想到的任何其他东西。不过,请记住引用您的参数扩展,我建议您single引用您的陷阱:trap 'rm -rf "$TMPDIR"'退出
  • 单引号,因为如果稍后您在脚本中因环境原因决定清理和更改 TMPDIR,那么您的陷阱仍然有效。
  • @AaronDigulla 为什么 $() 与反引号很重要?
  • @AlexanderTorstling 代码应始终使用单引号,以防止注入导致任意代码执行。如果您将数据扩展为 bash 代码 STRING,那么该数据现在可以执行任何代码执行的操作,这会导致纯空白错误以及破坏性错误,例如出于奇怪的原因清除您的 homedir 或引入安全漏洞。请注意,trap 采用一串 bash 代码,稍后将按原样进行评估。所以稍后当陷阱触发时,单引号将消失,只有语法双引号。
【解决方案2】:

您可以设置“trap”在退出时执行或在 control-c 上执行清理。

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

另外,我最喜欢的 unix-isms 之一是打开一个文件,然后在你仍然打开它的时候删除它。该文件保留在文件系统上,您可以对其进行读写,但是一旦您的程序退出,该文件就会消失。不过,不确定如何在 bash 中做到这一点。

顺便说一句:我将支持 mktemp 而不是使用您自己的解决方案的一个论点:如果用户预计您的程序将创建巨大的临时文件,他可能希望将 TMPDIR 设置为更大的位置,例如 /var /tmp。 mktemp 认识到,您的手动解决方案(第二个选项)没有。例如,我经常使用TMPDIR=/var/tmp gvim -d foo bar

【讨论】:

  • 使用 Bash,exec 5<>$TMPFILE 将文件描述符 5 绑定到 $TMPFILE 以进行读写,之后您可以使用 <&5>&5/proc/$$/fd/5 (Linux)。唯一的问题是 Bash 缺少seek 功能...
  • 关于trap 的几点说明:无法捕获SIGKILL(根据设计,它会立即终止执行)。因此,如果可能发生这种情况,请制定一个后备计划(例如tmpreaper)。其次,陷阱不是累积的——如果你有多个动作要执行,它们都必须在trap 命令中。处理多个清理操作的一种方法是定义一个函数(如果需要,您可以在程序进行时重新定义它)并引用它:trap cleanup_function EXIT
  • 我必须使用trap "rm -f $LOCKFILE" EXIT 否则我会收到意外的文件结束错误。
  • Shellcheck 警告要使用单引号,当调用陷阱时,表达式将用双引号“现在”扩展,而不是稍后。
  • 是的,最好使用单引号并将trap 语句放在脚本的最开头(shebang 之后)。 trap 'rm -f -- "$lockfile"' EXIT 非常准确。
【解决方案3】:

您想使用trap 命令来处理退出脚本或CTRL-C 之类的信号。详情请见Greg's Wiki

对于您的临时文件,使用basename $0 是一个好主意,同时提供一个模板来为足够的临时文件提供空间:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT

【讨论】:

  • 不要陷在 TERM/INT 上。 EXIT 上的陷阱。试图根据收到的信号来预测退出条件是愚蠢的,而且绝对不是包罗万象的。
  • 次要:使用 $() 而不是单个反引号。并在 $0 周围加上双引号,因为它可能包含空格。
  • 好吧,这条评论中的反引号可以正常工作,但这是一个公平的观点,养成使用$() 的习惯是件好事。也添加了双引号。
  • 您可以将整个子程序替换为 TMP1=$(tempfile -s "XXXXXX")
  • @RuslanKabalin 并非所有系统都有tempfile 命令,而我所知道的所有合理的现代系统都有mktemp 命令。
【解决方案4】:

请记住,选择的答案是bashism,这意味着解决方案为

trap "{ rm -f $LOCKFILE }" EXIT

只能在 bash 中工作(如果 shell 是 dash 或经典的 sh,它不会捕获 Ctrl+c),但如果你想要兼容性,那么你仍然需要枚举你想要捕获的所有信号。

另外请记住,当脚本退出时,总是会执行信号“0”(又名 EXIT)的陷阱,从而导致 trap 命令的双重执行。

如果有 EXIT 信号,不将所有信号堆叠在一行中的原因。

为了更好地理解它,请查看以下脚本,该脚本无需更改即可跨不同系统运行:

#!/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 信号处运行一些代码(最后阶段退出)

【讨论】:

    【解决方案5】:

    使用带有 $$ 的可预测文件名的替代方法是一个巨大的安全漏洞,您永远不应该考虑使用它。即使它只是您单用户 PC 上的一个简单的个人脚本。这是一个你不应该养成的非常坏的习惯。 BugTraq 充满了“不安全的临时文件”事件。有关临时文件安全方面的更多信息,请参阅 hereherehere

    我最初考虑引用不安全的 TMP1 和 TMP2 分配,但转念一想可能会not be a good idea

    【讨论】:

    • 如果可以的话,我愿意:+1 表示安全建议,另一个 +1 表示不引用坏主意和参考
    【解决方案6】:

    我不敢相信有这么多人认为文件名不包含空格。如果将 $TMPDIR 分配给“临时目录”,世界将会崩溃。

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

    文件名中的空格和其他特殊字符(如单引号和回车符)应在代码中被视为良好编程习惯的要求。

    【讨论】:

    • 需要注意的是,${parameter@operator} 扩展是在 Bash 4.4(2016 年 9 月发布)中添加的。
    • 等效于 Bash trap "rm -f $(printf %q "$zTemp")" EXIT
    • 这是最好的答案,因为无需担心zTemp 的值会在脚本后面被更改。此外,zTemp 可以声明为 local 给函数;它不必是全局脚本变量。
    【解决方案7】:

    我更喜欢使用tempfile,它以安全的方式在 /tmp 中创建一个文件,您不必担心它的命名:

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

    【讨论】:

    • tempfile 虽然更安全,但遗憾的是非常不可移植,因此最好避免使用它或至少模仿它。
    【解决方案8】:

    您不必费心删除那些用 mktemp 创建的 tmp 文件。无论如何,它们稍后会被删除。

    如果可以,请使用 mktemp,因为它会生成更多唯一文件,然后是 '$$' 前缀。它看起来更像是创建临时文件然后显式将它们放入 /tmp 的跨平台方式。

    【讨论】:

    • 在一段时间后被操作|文件系统本身删除
    • 魔法?一个定时任务?还是重新启动的 Solaris 机器?
    • 可能是其中之一。如果临时文件没有被某些中断删除(不会太频繁),有一天 tmp 文件将被删除 - 这就是他们称为 temp 的原因。
    • 你不能,不应该,也不能假设放在 /tmp 中的东西会永远保留在那里;同时,你不应该假设它会神奇地消失。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-14
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多