欢迎大家来到IT世界,在知识的湖畔探索吧!
似乎没有任何单一命令可以在所有地方简单地工作。tempfile 不是可移植的。mktemp 存在更广泛(但仍不普及),但它可能需要一个 -c 开关来预先创建文件;或者它可能默认创建文件并在提供 -c 时出错。一些系统都没有这两个命令(Solaris、POSIX)。POSIX 系统应该有 m4,它具有创建临时文件的能力,但有些系统可能默认不安装 m4,或者它们的 m4 实现可能缺少此功能。
传统的答案通常是这样的:
# 不要使用!竞态条件! tempfile=/tmp/myname.$ trap 'rm -f -- "$tempfile"; exit 1' 1 2 3 15 rm -f -- "$tempfile" touch -- "$tempfile"
欢迎大家来到IT世界,在知识的湖畔探索吧!
问题是:如果文件已经存在(例如作为指向 /etc/passwd 的符号链接),则脚本可能会将东西写入不应写入的位置。即使您在使用它之前立即删除文件,您仍然会有竞争条件:在您的 shell 命令之间的时间间隔中,某人可能会重新创建恶意符号链接。
使用您的 $HOME
最佳的可移植答案是将临时文件放在您的主目录(或其他私有目录,例如 $XDG_RUNTIME_DIR)中,没有其他人有写入权限。然后至少您不必担心恶意用户。基于 PID 的简单方案(或共享文件系统的主机名 + PID)应足以防止与您自己的脚本发生冲突。
如果您正在实现在没有主目录的用户帐户下运行的守护程序,为什么不在安装代码的同时为您的守护程序创建一个私有目录?
不幸的是,人们似乎不喜欢这个答案。他们要求将临时文件放在 /tmp 或 /var/tmp 中。对于这些人,没有清洁的答案,所以他们必须选择一种可以接受的 hack。
创建临时目录
如果您无法使用 $HOME,则下一个最佳答案是创建一个私有目录来保存您的临时文件,而不是直接在可写的世界沙盒(例如 /tmp 或 /var/tmp)中创建文件。mkdir 命令是原子的,并且仅在实际创建目录时报告成功。只要我们不使用 -p 选项,我们就可以确保它实际上创建了一个全新的目录,而不是跟随到危险的符号链接。
以下是这种方法的一个示例:
欢迎大家来到IT世界,在知识的湖畔探索吧!# Bash i=0 tempdir= trap '[[ $tempdir ]] && rm -rf -- "$tempdir"' EXIT while ((++i <= 10)); do tempdir=${TMPDIR:-/tmp}//$RANDOM-$ mkdir -m 700 -- "$tempdir" 2>/dev/null && break done if ((i > 10)); then printf 'Could not create temporary directory\n' >&2 exit 1 fi
如果需要在 POSIX 系统上运行,则可以使用 awk 以兼容 POSIX 的方式生成随机数:
# POSIX i=0 tempdir= cleanup() { [ "$tempdir" ] && rm -rf -- "$tempdir" if [ "$1" != EXIT ]; then trap - "$1" # reset trap, and kill "-$1" "$" # resend signal to self fi } for sig in EXIT HUP INT TERM; do trap "cleanup $sig" "$sig" done while [ "$i" -lt 10 ]; do tempdir=${TMPDIR:-/tmp}//$(awk 'BEGIN { srand (); print rand() }')-$ mkdir -m 700 -- "$tempdir" 2>/dev/null && break sleep 1 i=$((i+1)) done if [ "$i" -ge 10 ]; then printf 'Could not create temporary directory\n' >&2 exit 1 fi
请注意,srand() 函数使用自纪元以来的秒数来初始化随机数生成器,这对攻击者来说相当容易预测和执行拒绝服务攻击。(在 POSIX 之前的历史版 awk 实现可能甚至不使用当天的时间作为 srand() 的种子,因此如果您使用的是古老的系统,则不要依赖这一点。)
某些系统的文件名长度限制为 14 个字符,因此避免将 $RANDOM 字符串连接多次。您依赖的是 mkdir 的原子性而不是您的随机名称的晦涩性来保证安全性。如果有人用数十万个随机数文件填满 /tmp 以阻碍您,那么您就有更大的问题。
在某些系统(如 Linux)中:
- 您可以使用 mktemp 命令及其 -d 选项,以便仅为您创建临时目录,其中包含随机字符以使攻击者几乎无法预测目录名称。
- 您可以在 /tmp 中创建长度超过 14 个字符的文件名。
欢迎大家来到IT世界,在知识的湖畔探索吧!# Bash on Linux unset -v tempdir trap '[ "$tempdir" ] && rm -rf -- "$tempdir"' EXIT tempdir=$(mktemp -d -- "${TMPDIR:-/tmp}//XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || { printf 'ERROR creating a temporary file\n' >&2; exit 1; }
然后,您可以在临时目录中创建特定的文件。
使用特定于平台的工具
如果您只为一组特定的系统编写脚本,并且如果这些系统具有可以正常工作的 mktemp 或 tempfile 工具,则可以使用该工具。确保您的工具实际上创建了临时文件,而不仅仅是提供了一个未使用的名称。选项会因系统而异,因此您将必须考虑这一点编写脚本。由于一些平台都没有这些工具,因此这不是可移植的解决方案,但通常已经足够使用,特别是如果您的脚本仅针对特定操作系统。
以下是使用 Linux 的 mktemp 的示例:
# Bash on Linux unset -v tmpfile trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT tmpfile=$(mktemp)
使用 m4
m4 方法在理论上符合 POSIX 标准,但在实践中可能无法在 MacOS 上工作,因为其 m4 版本太老(截至 2021 年 7 月)。尽管如此,以下是一个示例。请注意,mkstemp 需要一个包含路径前缀的模板,否则它将在当前目录中创建临时文件。
# Bash die() { printf >&2 '%s\n' "$*"; exit 1; } unset -v tmpfile trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT tmpfile=$(m4 - <<< 'mkstemp(/tmp/foo-XXXXXX)') || die "couldn't create temporary file"
或者,您也可以使用以下替代方法
# Bash die() { printf >&2 '%s\n' "$*"; exit 1; } unset -v tmpfile trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT : "${TMPDIR:=/tmp}" tmpfile=$TMPDIR//$(HOME=$TMPDIR cd && m4 - <<< 'mkstemp(foo-XXXXXX)') || die "couldn't create temporary file"
其他方法
另一个不太严肃的建议是在脚本中包含 C 代码,该代码基于 mktemp(3) 库函数实现 mktemp(1) 命令,然后进行编译并在脚本中使用。但这有几个问题:
- 我们需要一个临时文件名来保存编译器的输出,而我们需要编译器的输出来获取临时文件名,这就是鸡生蛋的问题。
- 那些无用的 Solaris 系统可能没有 C 编译器。
了解一线大厂脚本编程实践
如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看《shell脚本编程最佳实践》专栏,专栏里有更多的实用小技巧和脚本代码分享。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/59334.html