有没有更好的方法在bash脚本中创建临时文件?
我通常会随意命名,比如tempfile-123,因为脚本结束时它会被删除。除了覆盖当前文件夹中可能存在的tempfile-123之外,这样做还有什么缺点吗?或者以更加谨慎的方式创建临时文件是否有优势?
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等模块。
mktemp
最安全和跨平台的方法似乎是与basename
结合使用,像这样:mktemp -dt "$(basename $0).XXXXXXXXXX"
。如果不使用basename
,则可能会出现以下错误:_mktemp: invalid template, '/tmp/MOB-SAN-JOB1-183-ScriptBuildTask-7300464891856663368.sh.XXXXXXXXXX', contains directory separator_
。 - i4niacmktemp -dt "$(basename $0).XXXXXXXXXX"
是正确的方式。意思是创建一个以当前脚本名为前缀且后面跟10个随机字符的临时目录。 - i4niac$0
,因为在OS X系统中有很多空格。mktemp -dt "$(basename "$0").XXXXXX"
的意思是创建一个以脚本名称为前缀的临时目录。 - Orwellophiletrap "rm -rf $mydir" EXIT
。 - KumZ你可能需要查看mktemp
mktemp
实用程序会获取给定的文件名模板,然后覆盖一部分以创建一个唯一的文件名。 模板可以是任何带有一些附加了“Xs”的文件名,例如/tmp/tfile.XXXXXXXXXX。 结尾的“Xs”将被替换为当前进程号和随机字母的组合。
更多详细信息请参见:man mktemp
创建临时文件通常在临时目录中进行(例如/tmp
),其他所有用户和进程都具有读写访问权限(任何其他脚本都可以在其中创建新文件)。因此,脚本应该小心地创建文件,例如使用正确的权限(例如仅对所有者为只读,参见:help umask
)并且文件名不易被猜测(理想情况下是随机的)。否则,如果文件名不唯一,可能会与多次运行相同的脚本产生冲突(例如竞态条件),某些攻击者可能会劫持某些敏感信息(例如当权限过于开放且文件名易于猜测时)或创建/替换其自己版本的代码文件(例如根据存储的内容替换命令或SQL查询)。
您可以使用以下方法创建临时目录:
TMPDIR=".${0##*/}-$$" && mkdir -v "$TMPDIR"
或临时文件:
TMPFILE=".${0##*/}-$$" && touch "$TMPFILE"
然而,它仍然是可预测的,不被视为安全。
根据man mktemp
中的说明:
传统上,许多shell脚本将程序名称和pid作为后缀并将其用作临时文件名。这种命名方案是可预测的,创建的竞争条件很容易被攻击者获胜。
因此,为了安全起见,建议使用mktemp
命令来创建唯一的临时文件或目录(-d
)。
${0##*/}
和$$
扩展到什么,或者链接一些相关的文档资料。 - jpbochi${0##*/}
是当前正在运行的脚本文件名,$$
是其进程 ID。一般来说,我认为这种解决方案并不是一个好方法,因为它可能会在脚本被执行的各个目录中留下隐藏的文件和文件夹。临时文件和文件夹可以在之后删除,但如果脚本被停止(使用 CTRL-C
),那么它们将保留下来。最好的做法是使用 mktemp
,因为它会在系统的临时文件夹中创建临时文件。 - Dave Fmktemp
可能是最通用的,特别是如果您计划长时间使用该文件。
如果您只需将文件暂时作为另一个命令的输入使用,则还可以使用进程替换操作符 <()
,例如:
$ diff <(echo hello world) <(echo foo bar)
mktemp
,并确保在此之后进行清理。通常的做法是使用trap
,它允许您设置一个钩子,在脚本被中断时可以运行。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 ...
除了ERR
和EXIT
之外,您可能还想设置其他信号;显然,kill -9
无法被捕获(这就是为什么除非紧急情况否则不应使用它)。当您的脚本会话挂起时或用户按下ctrl-C时,将生成HUP
(信号1)和INT
(信号2)。TERM
(信号15)是由kill
发送的默认信号,并请求终止脚本。
mktemp -p
取代了被视为过时的mktemp -t
。 -d
选项表示要创建一个目录;如果您只需要一个临时文件,那么这是不必要的。
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
时
使用此命令的输出来创建新文件本质上是不安全的,因为在生成名称和使用它之间存在时间窗口,另一个进程可以使用相同的名称创建对象。
tempfile(1)
的系统上,您也可以使用它。 - Dennis Williamson