如何编写幂等的Bash脚本(函数)? · Fatih Arslan


当你你写了一个bash脚本,但是由于错误而运行一半退出了,当您修复了系统中的错误并再次运行这个脚本。但是脚本中的一半步骤会立即失败,因为它们已经作用于您的系统了。要构建弹性系统,您需要编写幂等的软件。(幂等在分布式环境同样重要,这样才能保证重试等正确实现)

什么是幂等?
幂等脚本可以多次调用,每次调用它都会对系统产生相同的影响。这意味着,第二次调用将以相同的结果退出,并且不会产生任何副作用:

幂等:表示一个集合的元素,当它自身相乘或以其他方式操作时,其值不变。

良好的软件总是以幂等方式编写,特别是如果您在分布式系统中工作,其中操作可能最终是一致性的,并且由于重复请求(例如在具有交付保证的队列中At-Least-Once),您最终可能会多次调用同一个函数。

Bash实现
让我展示一些bash技巧,可以用来改变你的脚本为幂等的。

1.创建一个空文件
这意味着您可以多次调用它而不会出现任何问题。第二次调用不会对文件内容产生任何影响。请注意,虽然它会更新文件的修改时间,但如果您依赖它,请小心。

touch example.txt

2.创建目录
切勿直接使用mkdir,而应将其与-p一起使用。如果目录存在,此标志确保mkdir不会出错:

mkdir -p mydir

3. 创建符号链接
通常做法:

ln -s source target

但是如果你再次在同一个目标上调用它,会失败。为了使其幂等,传递-f:

ln -sf source target

-f标志在创建符号链接之前删除目标符号,因此它将始终成功。
链接目录时,您也需要传递-n。否则再次调用它将在目录中创建一个符号链接。

mkdir a
ln -sf a b
ln -sf a b
ls a
a

所以为了安全起见,请始终使用ln -sfn source target。

4.删除文件
传统:

rm example.txt

使用-f 忽略不存在的文件的标志。
rm -f example.txt

5.修改文件
有时您正在向现有文件添加新的一行。如果再次运行这个脚本,则需要确保不要再次添加刚才添加的一行。假设你的脚本这样:

echo "/dev/sda1 /mnt/dev ext4 defaults 0 0" | sudo tee -a /etc/fstab

如果再次运行,您最终会有重复的条目/etc/fstab。使这个幂等的一种方法是确保通过以下方式检查某些占位符grep:

if ! grep -qF "/mnt/dev" /etc/fstab; then
  echo
"/dev/sda1 /mnt/dev ext4 defaults 0 0" | sudo tee -a /etc/fstab
fi

这里的-q意思是静音模式和-F启用fixed string模式。如果/mnt/dev不存在,Grep将默默地失败,因此永远不会调用echo语句。
(数据库操作同理)

6.检查变量,文件或目录是否存在
大多数情况下,您会写入目录,从文件读取或使用变量进行简单的字符串操作。例如,您可能有一个基于某些输入创建新文件的工具:

echo "complex set of rules" > /etc/conf/foo.txt

文件操作可能是一项昂贵的操作,因此您不希望每次调用脚本时都要编写它。要使其幂等,请通过shell -f的内置test属性的标志检查文件是否存在:

if [ ! -f "/etc/conf/foo.txt" ]; then
 echo
"complex set of rules" > /etc/conf/foo.txt
fi

这里-f只是一个例子,你可以针对不同期刊使用许多其他标志,例如:
  • -d: 目录
  • -z:长度为零的字符串
  • -p:管道
  • -x:file并具有执行权限

假设您要安装二进制文件,但只有在主机中不存在二进制文件时,您才能使用-x如下所示:

# install 1password CLI
if ! [ -x "$(command -v op)" ]; then
  export OP_VERSION=
"v0.5.6-003"
  curl -sS -o 1password.zip https:
//cache.agilebits.com/dist/1P/op/pkg/${OP_VERSION}/op_linux_amd64_${OP_VERSION}.zip
  unzip 1password.zip op -d /usr/local/bin
  rm -f 1password.zip
fi

这会将op二进制文件安装到/ usr / local / bin。如果您重新运行脚本,它将不再安装它。另一个好处是,只需将二进制文件从系统中删除,更新OP_VERSION env并重新运行脚本,即可轻松将二进制文件升级到新版本。

7.格式化设备
要格式化卷,例如ext4格式化,一般使用如下命令:

mkfs.ext4 "$VOLUME_NAME"

如果再次调用它会立即失败。为了使这个调用是幂等的,我们在它前面添加blkid:

blkid "$VOLUME_NAME" || mkfs.ext4 "$VOLUME_NAME"

此命令打印给定块设备的属性。因此,预先基本上意味着仅在blkid失败时继续格式化,这表示给定的卷尚未格式化。

8.安装设备
尝试将卷装入现有目录:

mount -o discard,defaults,noatime "$VOLUME_NAME" "$DATA_DIR"

如果它已经安装,这将失败。一种方法是检查mount命令的输出并查看卷是否已经安装。但是有一种更好的方法可以做到这一点。使用mountpoint命令:

if ! mountpoint -q "$DATA_DIR"; then
  mount -o discard,defaults,noatime
"$VOLUME_NAME" "$DATA_DIR"
fi


总结
从长远来看,创建幂等且有弹性的软件总是有益的。因此,了解它们是有用的。最近我在bootstrap.sh 脚本中使用了上述所有提示和技巧 ,用于创建和配置我的远程开发机器。我知道我可以使用更复杂的工具从头开始配置VM,但有时候你需要一个简单的bash脚本。