在意外的bash退出中删除创建的临时文件

ski*_*inp 86 bash exit temporary-files

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

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

TMP1=`mktemp -p /tmp`
TMP2=`mktemp -p /tmp`
...
Run Code Online (Sandbox Code Playgroud)

TMP1=/tmp/`basename $0`1.$$
TMP2=/tmp/`basename $0`2.$$
...
Run Code Online (Sandbox Code Playgroud)

或者是否有更好的解决方案?

Chr*_*Lee 105

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

MYTMPDIR=$(mktemp -d)
trap "rm -rf $MYTMPDIR" EXIT
Run Code Online (Sandbox Code Playgroud)

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

  • +1肯定在EXIT上使用陷阱,而不是愚蠢的TERM/INT/HUP /你能想到的任何其他东西.虽然,记得**引用**你的参数扩展,我也会*建议你*单*引用你的陷阱:陷阱'rm -rf"$ TMPDIR"'退出 (26认同)
  • 小点:使用$()而不是单个反引号. (11认同)
  • 单引号,因为如果稍后你的脚本中你的陷阱将继续工作,你决定清理和更改TMPDIR因为环境. (7认同)
  • @AaronDigulla 为什么 $() 与反引号很重要? (3认同)
  • @OgrePsalm33:http://stackoverflow.com/questions/4708549/shell-programming-whats-the-difference-between-command-and-command (3认同)
  • @AlexanderTorstling代码应该始终是单引号,以防止注入导致任意代码执行.如果您将数据扩展为bash代码STRING,那么该数据现在可以执行任何代码,这会导致无辜的错误和空白,但也会出现破坏性错误,例如因奇怪的原因清除homedir或引入安全漏洞.请注意,trap会使用一串bash代码,这些代码将在以后进行评估.所以稍后当陷阱发生时,单引号将会消失,并且只会出现语法双引号. (3认同)

Pau*_*lin 82

您可以设置一个" 陷阱 "在退出时执行或在control-c上执行清理.

trap "{ rm -f $LOCKFILE; }" EXIT
Run Code Online (Sandbox Code Playgroud)

或者,我最喜欢的一个unix-isms是打开一个文件,然后在你打开文件时删除它.该文件保留在文件系统上,您可以读取和写入该文件,但是一旦程序退出,该文件就会消失.但是不知道你怎么在bash中这样做.

BTW:我将支持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缺乏"寻求"功能...... (8认同)
  • 关于`trap`的几个注释:没有办法陷入`SIGKILL`(按设计,因为它立即终止执行).因此,如果可能发生这种情况,请制定一个后备计划(例如`tmpreaper`).其次,陷阱不是累积的 - 如果你要执行多个动作,它们都必须在`trap`命令中.处理多个清理操作的一种方法是定义一个函数(如果需要,可以在程序进行时重新定义它)并引用:`trap cleanup_function EXIT`. (4认同)
  • Shellcheck 警告使用单引号,表达式将“现在”扩展为双引号,而不是在调用陷阱时稍后扩展。 (3认同)

Bri*_*ell 22

您希望使用trap命令来处理退出脚本或CTRL-C之类的信号.有关详细信息,请参阅" 高级Bash脚本编制指南 ".

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

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Run Code Online (Sandbox Code Playgroud)

  • @RuslanKabalin并非所有系统都有`tempfile`命令,而我所知道的所有合理的现代系统都有`mktemp`命令. (4认同)
  • 不要在 TERM/INT 上陷入困境。退出时陷入困境。试图根据收到的信号来预测退出条件是愚蠢的,而且绝对不是包罗万象的。 (2认同)
  • 小点:使用$()而不是单个反引号.并将双引号放在$ 0附近,因为它可能包含空格. (2认同)

Pau*_*aul 9

好习惯是美丽的

  • 避免假设变量的值在某个超远的时间永远不会改变(特别是如果这样的错误会引发错误)。

  • 如果适用于您的代码,请立即使 trap 扩展变量的值。传递给trap单引号的任何变量名称都会延迟其值的扩展,直到捕获之后。

  • 避免假设文件名不包含任何空格。

  • 使用 Bash${VAR@Q}$(printf '%q' "$VAR")克服由空格和其他特殊字符(例如文件名中的引号和回车符)引起的问题。

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

  • 应该注意的是,“${parameter@operator}”扩展是在 Bash 4.4(2016 年 9 月发布)中添加的。 (3认同)
  • 相当于 Bash &lt; v4.4:`trap "rm -f $(printf %q "$zTemp")" EXIT` (2认同)

Ale*_*lex 7

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

trap "{ rm -f $LOCKFILE }" EXIT
Run Code Online (Sandbox Code Playgroud)

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

还要记住,当脚本退出陷阱时,始终执行信号"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 
Run Code Online (Sandbox Code Playgroud)

这个解决方案将为您提供更多控制,因为您可以在最终退出(preExit函数)之前运行实际信号时运行一些代码,如果需要,您可以在实际EXIT信号上运行一些代码(退出的最后阶段)


hlo*_*dal 5

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

我最初考虑引用不安全的 TMP1 和 TMP2 分配,但转念一想,这可能不是一个好主意