写点什么

在 POSIX sh 中替代 Bash 的 [[ ... ]]:脚本兼容性实践

作者:qife122
  • 2025-12-04
    福建
  • 本文字数:2725 字

    阅读完需:约 9 分钟

文章正文 markdown 格式数据

POSIX sh 中替代使用 Bash 的[[ ... ]]的方法

提问:


我正在使用以下代码来解析传递给脚本的第一个参数。它具有错误处理功能,并且工作方式完全符合我的要求:


if [ -z "$action" ]; then    printf "[${c_RED}ERROR${c_RESET}] The action must be specified.\n" && exit 1elif [[ "$action" =~ ^-{0,2}[Hh][Ee][Ll][Pp]$ ]] || [[ "$action" =~ ^-{0,2}[Hh]$ ]]; then    printf "Usage: pocsag [ACTION] [INPUTMETHOD/REDACTION] [OUTPUTMETHOD/PATHTOFILE/SERVICEACTION]                              "    printf "Examples:                                                                                                           "    printf "  pocsag decode rtlsdr cli                                                                                          "    printf "  pocsag decode netcat file                                                                                         "    printf "  pocsag redact medical ~/media/signals/pocsag/decoded/POCSAG*                                                      "    printf "  pocsag service rtlsdr start                                                                                       "    printf "                                                                                                                    "    printf "Actions:                                                                                                            "    printf "  decode                    Envoke the usage of the input tuner, sox and multimon-ng to decode the signals.         "    printf "  redact                    Copy file but redact regex matching lines of a file. For example: Removing medical TXs. "    printf "  service                   Used to start/stop the systemd service in user's ~/.config. Relies on rtlsdr_pager_rx   "    printf "                                                                                                                    "    printf "Input Methods:                                                                                                      "    printf "  rtlsdr                    Use an RTLSDR device plugged into the local computer.                                   "    printf "  netcat                    Listen to localhost:7355 using netcat, then process and output locally.                 "    printf "                                                                                                                    "elif ! [[ "$action" =~ ^([Dd][Ee][Cc][Oo][Dd][Ee]|[Rr][Ee][Dd][Aa][Cc][Tt]|[Ss][Ee][Rr][Vv][Ii][Cc][Ee])$ ]]; then    printf "[${c_RED}ERROR${c_RESET}] The action must be 'decode', 'redact' or 'service'.\n" && exit 3fi
复制代码


现在我希望使这个脚本符合 POSIX 标准,因此不能使用[[ ]]这个 Bash 特有的语法。我应该如何实现这一点?使用复杂的 case 语句吗?肯定有更好的方法。

答案 2(得分:7)

exprawk是两个可以进行正则表达式匹配的 POSIX 工具。expr使用基本正则表达式¹,awk使用扩展正则表达式的变体²。expr存在许多设计缺陷,通常被认为已过时(甚至 POSIX 也建议不要使用它),因此最好避免使用。虽然几个 shell(至少是 zsh 和 yash)的[(即test)内置函数可以通过=~操作符进行正则表达式匹配,但这是 POSIX 标准之外的扩展,因此不能在 sh 脚本中使用。在这里,您可以定义使用awk进行正则表达式匹配的 shell 辅助函数match(以及用于不区分大小写变体的imatch):


match() {  awk -- 'BEGIN{exit(ARGV[1] !~ ARGV[2])}' "$@"}imatch() {  awk -- 'BEGIN{exit(tolower(ARGV[1]) !~ tolower(ARGV[2]))}' "$@"}
if imatch "$action" '^-{0,2}h(elp)?$'; then...
复制代码


请注意,严格来说,imatch并不是进行不区分大小写匹配的适当方法,但对于仅 ASCII 输入和正则表达式来说已经足够好了。在这里,使用case结构可能同样简单,并且使其更清晰:


case $action in  ([hH] | -[hH] | --[hH] | [Hh][Ee][Ll][Pp] | -[Hh][Ee][Ll][Pp] | --[Hh][Ee][Ll][Pp]) ...;;esac
复制代码


您也可以先删除一个或两个前导的-并转换为小写:


tolower() {  awk -- 'BEGIN{for (i = 1; i < ARGC; i++) print tolower(ARGV[i])}' "$@"}action=${action#-} action=${action#-}action=$(tolower "$action")
case $action in (h | help) ...esac
复制代码


这里使用awktolower()进行大小写转换。POSIX 工具集中的替代方案包括dd conv=lcasetr '[:upper:]' '[:lower:]',但在 GNU 工具集中,截至撰写本文时,只有awk版本适用于多字节字符。³请注意,[[...]]结构最初来自 ksh,而不是 bash。它已被包括 zsh、bash、yash、busybox ash 在内的几个 shell 复制,并有许多变体。其中的正则表达式匹配首先于 2004 年在 zsh 中添加了-pcre-match操作符(PCRE 具有不区分大小写匹配的语法),然后是 bash 于 2005 年在 3.1 版本中添加了=~⁴(进行 ERE 匹配)。后来=~被添加到更多 shell 中,包括 zsh 和 ksh93。在 zsh 中,对于=~,您可以通过(取消)设置rematchpcre选项来选择 ERE 或 PCRE。ksh93 的模式匹配在 2006 年的 ksh93r+版本中扩展为具有正则表达式匹配语法⁵,因此例如您可以执行[[ $action = ~(Ei)^-{0,2}h(elp)?$ ]](在zmodload zsh/pcre之后,相当于 zsh 的[[ $action -pcre-match '(?i)^-{0,2}h(elp)$' ]])。


¹ 使用其:操作符。另请注意,expr正则表达式匹配隐式地在开头锚定(就像有一个隐藏的^),而不是在结尾。² 除了标准 ERE 外,还识别像\n/\b/\123...这样的内容,这意味着除了 busybox awk 之外,您无法获得反向引用(标准 ERE 中也没有反向引用)。请注意,对区间操作符({x,y})的支持在一些 awk 实现中相对较晚才添加(就 mawk 而言是非常近期)。³ 不过,已知一些 GNU/Linux 发行版维护了解决此问题的补丁,因此您的体验可能有所不同。请注意,Ubuntu 上的默认 awk 实现 mawk 也不支持多字节字符。⁴ =~可能源自 perl 中的相同操作符,而 perl 又可能源自 awk 的~操作符。⁵ 本质上,其=~操作符在底层执行一个前面附加了~(E)的模式=。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)


公众号二维码


办公AI智能小助手


公众号二维码


网络安全技术点滴分享


用户头像

qife122

关注

还未添加个人签名 2021-05-19 加入

还未添加个人简介

评论

发布
暂无评论
在POSIX sh中替代Bash的[[ ... ]]:脚本兼容性实践_posix_qife122_InfoQ写作社区