发布于 2026-01-06 10 阅读
0

编写不完全是 Bash 的 Bash 脚本:检查 Bash 特有行为并使用 Dash 进行测试

编写不完全是 Bash 的 Bash 脚本:检查 Bash 特有行为并使用 Dash 进行测试

像BashZsh这样的 shell 功能强大且易于使用,包含的功能远超简单的 POSIX 标准 shell。编写脚本时,充分利用 shell 的所有功能会很有帮助。

然而,在某些情况下,可移植性应该是一个有价值的特性,它允许脚本在各种 shell 上运行。

避免使用“bash特有语法”是提高bash脚本可移植性的关键。让我们来探讨如何编写符合POSIX标准的shell脚本,使其能够在Ash/Dash和其他shell中运行。

简要清单

以下是我用来跟踪自己剧本写作进度的清单。我的剧本是否:

  1. 脚本的#!/bin/sh第一行(“ shebang ”)必须是 `shell`#!/usr/bin/bash或其他 shell脚本。
  2. 避免使用双括号测试[[ ]],而应使用单括号测试。[ ]
  3. 当需要打印换行符时,请使用printf此方法。echo -e'\n'
  4. read除了 之外,不要使用其他标志-r,例如:read -r
  5. 避免使用 Bash 的便捷重定向:使用>myfile 2>&1`stdout` 和 `stderr` 重定向到文件,而不是使用 `stdout` 和 `stderr`。&>myfile
  6. 使用dash或posh准确测试:符合政策的普通壳
  7. 仅使用标准标志和选项来执行常用实用程序,例如sedgrepcuttest
  8. 避免shellcheck发现的问题

一个例子

#!/bin/sh

read -p "Who would you like to greet? "

if [[ -z $REPLY ]] ; then
  recipient="$REPLY"
else
  recipient="World"
fi

echo -e "Hello\n$recipient\n"

Enter fullscreen mode Exit fullscreen mode

您可以将以下内容保存到您选择的当前工作目录中,例如:example-noncompliant.sh

用人类语言来说,上面的脚本会提示输入问候语接收者,如果没有指定接收者,则将接收者设置为“世界”,然后在多行中向接收者致以问候。

上述代码在 Bash 中可以正常运行,但在其他 shell 中可能会出现问题。建议您在 Bash 中运行一次以确保万无一失。即使在Zsh中,也可能会引发一些错误。

Dash,一个符合 POSIX 标准的 shell。

Dash是Ash shell的衍生版本,旨在符合POSIX标准。

Debian 和 Ubuntu 默认安装了 dash。事实上,使用 `dash shell` 调用的脚本/bin/sh默认也会使用 dash 运行。在AlpineTiny Core LinuxOpenWRT以及其他默认使用BusyBox 的ash发行版中,标准 shell 也是 dash(尽管标签为 `dash` )。在 Fedora 中,可以使用 `dash shell` 安装 dash sudo dnf install dash。其他发行版也可能在其软件仓库中包含 dash。

使用DockerPodman运行 dash 非常简单,如下例所示:

docker run -it debian dash
Enter fullscreen mode Exit fullscreen mode

您还可以尝试我的POSIX playground 容器,其中包含各种工具,包括dash。默认情况下,它使用posh:符合策略的普通 SHell,它比 dash 略微严格一些。可以使用以下命令启动它:

docker run -it docker.io/bowmanjd/posix-playground
Enter fullscreen mode Exit fullscreen mode

详情请参阅原文

以上所有情况均可轻松podman替换。docker

测试示例

请尝试运行example-noncompliant.sh上面的脚本,但使用 dash 而不是 Bash?

dash example-noncompliant.sh
Enter fullscreen mode Exit fullscreen mode

或者,使用 Docker 或 Podman:

docker run -it -v "$(pwd):/work" debian dash /work/example-noncompliant.sh
Enter fullscreen mode Exit fullscreen mode

输出结果可能类似于:

dash: 3: read: arg count
dash: 5: [[: not found
-e Hello
Enter fullscreen mode Exit fullscreen mode

从该输出结果中可以得出一些学习要点。

避免双括号测试[[ ]]

如果使用 Bash 或其他支持 Bash 语法的 shell,这种[[构造是安全的。它具有一些便捷的功能,例如使用正则表达式匹配=~,并且在字符串匹配方面风险更低。

也就是说,通常情况下,使用单括号方法不会出错:[ ](`\` 的别名test)。务必始终引用变量,但这本身就是个好建议。如果需要常规的 Express 匹配,请使用 `\` grep

总之:并非所有 shell 都支持[[;请改用[其他方法。

使用香草read

使用该read命令获取输入时,以下是一些建议:

  1. 务必指定变量,而不要依赖 Bash 的默认值。$REPLY
  2. read -r使用此标志。使用此标志-r会禁止用户使用反斜杠转义字符,这可能会导致后续问题。POSIX 不支持read其他任何标志。
  3. 与其使用 `--prompt` 指定提示符-p,不如printf在命令之前调用 `--prompt` read。再次强调,POSIXread不支持这样的标志,而且该选项的含义与Zsh 的 `--prompt`-p不同read

根据这些规则,我们的脚本不应该使用read -p "Who would you like to greet? ",而应该使用:

printf "Who would you like to greet? "
read -r recipient
Enter fullscreen mode Exit fullscreen mode

printf当换行符出现问题时使用

echo当我们知道要输出一个简单的字符串,后面跟着一个换行符时,这个命令非常好用。

但是,如果我们要打印的字符串中有换行符,或者如果想要打印时不带尾随换行符,那么printf,而不是echo -e将是我们的朋友。

所以,与其echo -e "Hello\n$recipient\n"在上面的代码中这样写,不如这样写:

printf "Hello\n%s\n\n" "$recipient"
Enter fullscreen mode Exit fullscreen mode

注意第一个字符串(格式字符串)中的变量替换%s。不要像$recipient在格式字符串中那样直接使用 shell 变量。这是正确的方法。

重构示例

鉴于以上问题,让我们彻底重写问候语脚本:

#!/bin/sh

printf "Who would you like to greet? "
read -r recipient

if [ -z "$recipient" ] ; then
  recipient="World"
fi

printf "Hello\n%s\n\n" "$recipient"
Enter fullscreen mode Exit fullscreen mode

您可以将上述内容以文件名example-posix.sh或其他类似名称保存。

运行后,它的表现是否与不合规的脚本相同?希望不同;请测试一下。

令人满意。

用 bashisms 寻找checkbashisms

Debian devscripts项目中嵌入了一个名为checkbashisms 的工具。它是一个简单但功能强大的Perl 脚本,可以找出 shell 脚本中所有以#!/bin/sh shebang行开头的 bashisms代码。

在 Debian 和 Ubuntu 上,可以使用 ` sudo apt install devscripts--install` 命令安装;在 Fedora 上,可以使用`--install` 命令安装;而在sudo dnf install devscripts-checkbashismsAlpine上,则需要使用 `--install` 命令。sudo apk add checkbashisms其他发行版可能也有类似的方法。您也可以尝试安装 Perl,然后下载并运行checkbashisms Perl 脚本本身。

当你在……上运行它时会发生什么example-noncompliant.shexample-posix.sh这很能说明问题……

追求最佳实践shellcheck

我最近最喜欢的 shell 脚本辅助工具是Shellcheck。你可以把你的 shell 脚本粘贴到网上进行检查,或者像往常一样安装 shellcheck(Debian、Ubuntu、Fedora、Alpine、Archlinux 等系统的标准软件包仓库中都已提供该工具)。

它会检查所有需要 POSIX 合规性的代码行,但也会检查许多其他问题。你的代码可能运行正常,但存在一些需要注意的陷阱。Shellcheck可以帮你解决这个问题。我把它集成到我的编辑器中,这样我就可以边打字边检查代码了。

根据 POSIX 规范进行测量

值得庆幸的是,POSIX.1-2017 标准拥有公开文档。试想一下:当需要查找和测试某个工具(例如 `bash` readgrep`bash` 或`bash`)的选项sed时,与其查阅 GNU 文档、发行版手册页或 Bash/Zsh 文档,何不直接查阅 POSIX 规范本身呢?规范中对各种工具及其选项的列表都有清晰的解释。

如果您确实需要使用扩展工具提供的增强功能,可以选择使用它们。有了 POSIX 规范,您就可以做出明智的决定。

通常情况下,我发现我并不需要它,sed -E或者grep -E说我并不像想象中那么需要它。只需几个额外的转义字符,一切就搞定了。

考虑其他语言和配置工具

有时,当需要使用 GNU 工具提供的扩展语法时,这可能表明完成这项工作的正确工具根本不是 shell 及其同类工具。

如果你的系统安装了PythonRubyNodeJS或其他你喜欢的语言,这或许是一个更强大、更灵活、更一致的选择?即使在某些情况下(例如嵌入式系统),这些运行时环境可能过于臃肿,从另一台机器进行远程脚本编写或许也是个不错的选择。例如,可以使用 Python 通过 SSH 连接到远程机器,获取必要的信息,执行一些逻辑操作,然后将相应的命令发送回来,而无需在目标机器上安装 Python。

这就是AnsibleSaltstackChefPuppet等工具存在的原因。如果需求很简单,这些工具也可能显得过于臃肿。但它们在灵活性和可重复性方面是无可匹敌的。

其他资源

在为本文进行研究的过程中,我发现了一些资源,您可能会觉得它们至少和这篇文章一样有趣:

欢迎在评论区分享您的建议、问题和更正!

文章来源:https://dev.to/bowmanjd/writing-bash-scripts-that-are-not-only-bash-checking-for-bashisms-and-testing-with-dash-1bli