在Bash中创建临时文件

175

有没有更好的方法在bash脚本中创建临时文件?

我通常会随意命名,比如tempfile-123,因为脚本结束时它会被删除。除了覆盖当前文件夹中可能存在的tempfile-123之外,这样做还有什么缺点吗?或者以更加谨慎的方式创建临时文件是否有优势?


1
不要使用临时文件,而是使用临时目录。同时不要使用mktemp。请参考这里的原因:http://www.codeproject.com/Articles/15956/Security-Tips-for-Temporary-File-Usage-in-Applicat - ceving
25
@ceving 这篇文章完全是错的,至少在涉及 shell 命令 mktemp 时是这样(与库函数调用 mktemp 不同)。由于 mktemp 使用限制性的 umask 创建文件本身,所以只有当攻击者和被攻击者使用相同的账户时,才能成功利用给出的攻击方法...此时游戏已经输了。有关 shell 脚本编程最佳实践,请参阅 http://mywiki.wooledge.org/BashFAQ/062 - Charles Duffy
在拥有tempfile(1)的系统上,您也可以使用它。 - Dennis Williamson
嗨@CharlesDuffy。我很想将您的答案设为默认值 - 这是一个非常好的资源!谢谢! - Steven Dake
7个回答

202

mktemp(1)的手册页已经解释得非常清楚了:

传统上,很多Shell脚本使用程序名加上pid作为后缀来命名临时文件。这种命名方式是可预测的,由此产生的竞争条件很容易被攻击者利用。一种更安全、但仍然不够好的方法是使用相同的命名方案来创建一个临时目录。虽然这确保了临时文件不会被篡改,但仍允许进行简单的拒绝服务攻击。出于这些原因,建议使用mktemp。

在脚本中,我像这样调用mktemp:

mydir=$(mktemp -d "${TMPDIR:-/tmp/}$(basename $0).XXXXXXXXXXXX")

这个命令可以创建一个临时目录,你可以在其中安全地为实际文件命名并保持可读性与实用性。

mktemp 并不是标准的命令,但它在许多平台上都存在。 "X"通常会被转换为一些随机字符,更多字符可能会更随机;但是,一些系统(如busybox ash)对此限制得比其他系统更严格。


顺便说一句,安全创建临时文件不仅对于Shell脚本非常重要。这就是为什么Python有tempfile,Perl有File::Temp,Ruby有Tempfile等模块。


2
-t选项已被弃用:http://www.gnu.org/software/coreutils/manual/html_node/mktemp-invocation.html - Tibor Vass
7
使用mktemp最安全和跨平台的方法似乎是与basename结合使用,像这样:mktemp -dt "$(basename $0).XXXXXXXXXX"。如果不使用basename,则可能会出现以下错误:_mktemp: invalid template, '/tmp/MOB-SAN-JOB1-183-ScriptBuildTask-7300464891856663368.sh.XXXXXXXXXX', contains directory separator_ - i4niac
7
mktemp -dt "$(basename $0).XXXXXXXXXX" 是正确的方式。意思是创建一个以当前脚本名为前缀且后面跟10个随机字符的临时目录。 - i4niac
4
@i4niac提到需要引用$0,因为在OS X系统中有很多空格。mktemp -dt "$(basename "$0").XXXXXX"的意思是创建一个以脚本名称为前缀的临时目录。 - Orwellophile
13
在脚本执行结束时,清除临时目录可能会很好: trap "rm -rf $mydir" EXIT - KumZ
显示剩余5条评论

51

可以使用mktemp

它会在专门用于存储临时文件的文件夹内创建一个临时文件,并且会保证给你一个独特的文件名。它会输出该文件的名称:

> mktemp
/tmp/tmp.xx4mM3ePQY
>

19

你可能需要查看mktemp

mktemp实用程序会获取给定的文件名模板,然后覆盖一部分以创建一个唯一的文件名。 模板可以是任何带有一些附加了“Xs”的文件名,例如/tmp/tfile.XXXXXXXXXX。 结尾的“Xs”将被替换为当前进程号和随机字母的组合。

更多详细信息请参见:man mktemp


12

创建临时文件通常在临时目录中进行(例如/tmp),其他所有用户和进程都具有读写访问权限(任何其他脚本都可以在其中创建新文件)。因此,脚本应该小心地创建文件,例如使用正确的权限(例如仅对所有者为只读,参见:help umask)并且文件名不易被猜测(理想情况下是随机的)。否则,如果文件名不唯一,可能会与多次运行相同的脚本产生冲突(例如竞态条件),某些攻击者可能会劫持某些敏感信息(例如当权限过于开放且文件名易于猜测时)或创建/替换其自己版本的代码文件(例如根据存储的内容替换命令或SQL查询)。


您可以使用以下方法创建临时目录:

TMPDIR=".${0##*/}-$$" && mkdir -v "$TMPDIR"

或临时文件:

TMPFILE=".${0##*/}-$$" && touch "$TMPFILE"

然而,它仍然是可预测的,不被视为安全。

根据man mktemp中的说明:

传统上,许多shell脚本将程序名称和pid作为后缀并将其用作临时文件名。这种命名方案是可预测的,创建的竞争条件很容易被攻击者获胜。

因此,为了安全起见,建议使用mktemp命令来创建唯一的临时文件或目录(-d)。


3
确实,这确实提高了答案的质量。我已经给你点赞了,但是无法再投票了。我的建议是解释${0##*/}$$扩展到什么,或者链接一些相关的文档资料。 - jpbochi
@jpbochi ${0##*/} 是当前正在运行的脚本文件名,$$ 是其进程 ID。一般来说,我认为这种解决方案并不是一个好方法,因为它可能会在脚本被执行的各个目录中留下隐藏的文件和文件夹。临时文件和文件夹可以在之后删除,但如果脚本被停止(使用 CTRL-C),那么它们将保留下来。最好的做法是使用 mktemp,因为它会在系统的临时文件夹中创建临时文件。 - Dave F

8

mktemp 可能是最通用的,特别是如果您计划长时间使用该文件。

如果您只需将文件暂时作为另一个命令的输入使用,则还可以使用进程替换操作符 <(),例如:

$ diff <(echo hello world) <(echo foo bar)

3
为了更好地解释之前的答案,您需要运行mktemp,并确保在此之后进行清理。通常的做法是使用trap,它允许您设置一个钩子,在脚本被中断时可以运行。
Bash还提供了EXIT伪信号,以便您可以设置一个trap,在脚本成功退出时运行,以及ERR,如果您的脚本产生错误则会触发。(有关一些不明显的后果,请参见bash脚本中set -e的含义?)
t=$(mktemp -d -p temporary.XXXXXXXXXXXX) || exit
trap 'rm -rf "$t"; exit' ERR EXIT  # HUP INT TERM

: # use "$t" to your heart's content ...

除了ERREXIT之外,您可能还想设置其他信号;显然,kill -9无法被捕获(这就是为什么除非紧急情况否则不应使用它)。当您的脚本会话挂起时或用户按下ctrl-C时,将生成HUP(信号1)和INT(信号2)。TERM(信号15)是由kill发送的默认信号,并请求终止脚本。

mktemp -p取代了被视为过时的mktemp -t-d选项表示要创建一个目录;如果您只需要一个临时文件,那么这是不必要的。


1

mktemp文档提供了一些很好的示例。

如果您需要为临时文件指定特定的后缀(文件扩展名),可以执行以下操作:

$ myfile=$(mktemp --suffix ".txt")
$ echo "$myfile"
/tmp/tmp.9T9soL2QNp.txt

如果您不想创建文件,只需要一个名称,您可以使用-u / --dry-run标志。

$ myfile=$(mktemp -u --suffix ".txt")
$ echo "$myfile"
/tmp/tmp.Y8cMDJ1DDr.txt

但要注意,当使用-u/--dry-run

使用此命令的输出来创建新文件本质上是不安全的,因为在生成名称和使用它之间存在时间窗口,另一个进程可以使用相同的名称创建对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接