因此,我删除了我的主文件夹(或者更准确地说,我有权写入的所有文件)。发生的事情是我有
build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>
在 bash 脚本中,在不再需要之后$build
,删除声明及其所有用法 - 但rm
. Bash 很高兴地扩展到rm -rf /*
.是的。
我觉得很愚蠢,安装了备份,重新完成了我丢失的工作。试图摆脱耻辱。
现在,我想知道:编写 bash 脚本的技术是什么,可以使此类错误不会发生,或者至少不太可能发生?例如,如果我写了
FileUtils.rm_rf("#{build}/*")
在 Ruby 脚本中,解释器会抱怨build
没有被声明,所以语言保护了我。
除了畜栏之外,我在 bash 中考虑过的内容rm
(正如相关问题中的许多答案提到的那样,这并不是没有问题的):
rm -rf "./${build}/"*
那会毁掉我当前的工作(Git 存储库),但除此之外什么也没有。rm
当在当前目录之外操作时,其变体/参数化需要交互。 (找不到任何。)类似的效果。
是这样吗?还是有其他方法可以编写在这个意义上“健壮”的 bash 脚本?
答案1
set -u
或者
set -o nounset
这将使当前 shell 将未设置变量的扩展视为错误:
$ unset build
$ set -u
$ rm -rf "$build"/*
bash: build: unbound variable
set -u
并且set -o nounset
是POSIX shell 选项。
一个空的值会不是但会触发错误。
为此,请使用
$ rm -rf "${build:?Error, variable is empty or unset}"/*
bash: build: Error, variable is empty or unset
的扩展${variable:?word}
将扩展到 的值,variable
除非它为空或未设置。如果它为空或未设置,则将word
显示在标准错误上,并且 shell 会将扩展视为错误(该命令将不会执行,如果在非交互式 shell 中运行,这将终止)。保留:
out 只会针对未设置的值触发错误,就像在 下一样set -u
。
${variable:?word}
是一个POSIX 参数扩展。
这些都不会导致交互式 shell 终止,除非set -e
(或set -o errexit
) 也有效。${variable:?word}
如果变量为空或未设置,则导致脚本退出。set -u
如果与 一起使用,将导致脚本退出set -e
。
至于你的第二个问题。没有办法限制rm
不在当前目录之外工作。
GNU 实现rm
有一个选项可以阻止它递归地删除已安装的文件系统,但我相信我们可以在不将调用包装在实际检查参数的函数中的情况下--one-file-system
获得最接近的结果。rm
作为旁注: ${build}
完全等同于$build
除非扩展作为字符串的一部分发生,其中紧随其后的字符是变量名称中的有效字符,例如在 中"${build}x"
。
答案2
我建议使用test
/进行正常的验证检查[ ]
如果您这样编写脚本,那么您就会安全:
build="build"
...
[ -n "${build}" ] || exit 1
rm -rf "${build}/"*
...
支票[ -n "${build}" ]
是"${build}"
非零长度字符串。
是||
bash 中的逻辑 OR 运算符。如果第一个命令失败,它会导致运行另一个命令。
这样,就${build}
已经是空的/未定义的/等等。脚本将退出(返回码为 1,这是一般错误)。
如果您删除了所有用途,这也会保护您,${build}
因为[ -n "" ]
永远都是假的。
test
使用/的优点[ ]
是它还可以使用许多其他更有意义的检查。
例如:
[ -f FILE ] True if FILE exists and is a regular file.
[ -d FILE ] True if FILE exists and is a directory.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.
答案3
在您的具体情况下,我过去修改了“删除”以移动文件/目录(假设 /tmp 与您的目录位于同一分区上):
# mktemp -d is also a good, reliable choice
trashdir=/tmp/.trash-$USER/trash-`date`
mkdir -p "$trashdir"
...
mv "${build}"/* "$trashdir"
...
在幕后,这会将顶级文件/目录引用从源移动到$trashdir
同一分区上的目标目录结构,并且不会花时间遍历目录结构并立即释放每个文件的磁盘块。这会在系统处于活动使用状态时产生更快的清理速度,以换取稍慢的重新启动速度(/tmp 在重新启动时被清理)。
或者,对于消耗大量磁盘空间的进程(例如,构建),定期清理 /tmp/.trash-$USER 的 cron 条目将防止 /tmp 被填满。如果您的目录与 /tmp 位于不同的分区上,您可以在您的分区上创建一个类似 /tmp 的目录,并让 cron 清理它。
但最重要的是,如果您以任何方式搞砸了变量,您可以在清理发生之前恢复内容。
答案4
我总是尝试用一行开始我的 Bash 脚本#!/bin/bash -ue
。
-e
意思是“第一次失败e错误”;
-u
意思是“第一次使用失败你n声明变量”。
在精彩文章中查找更多详细信息使用非官方的 Bash 严格模式(除非您喜欢调试)。作者还建议使用,set -o pipefail; IFS=$'\n\t'
但就我的目的而言,这有点过分了。