bash/zsh 脚本中“case”语句的奇怪语法的原因是啥?

Posted

技术标签:

【中文标题】bash/zsh 脚本中“case”语句的奇怪语法的原因是啥?【英文标题】:What is the reason for the weird syntax of the "case" statement in a bash/zsh script?bash/zsh 脚本中“case”语句的奇怪语法的原因是什么? 【发布时间】:2011-05-13 09:45:47 【问题描述】:

程序员的角度来看,shell 脚本只是另一种编程语言,人们必须学习并遵守该语言的规则。然而,我不得不承认,这种语法是我所见过的一种相当常用的语言中最奇怪的风格。 shell 是否从它所继承的旧语言中获取了这种语法?语法中是否有特殊含义/含义?

作为一个例子,这是我在 SO 上从another post 获取的一个小 sn-p

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    status)
        check_status
        ;;
    *)
        echo "Usage: $0 start|stop|restart|status"
        exit 1
        ;;
esac

看到这个,首先我可以看到caseesac 结尾,这是它的反转形式(如iffi 结尾)。其次,我知道每个案例后面都有一个)。很公平,但是为什么我在每个语句的末尾都需要两个;?我还要说没有伴随() 是丑陋的。

我正在寻找有关该语言历史方面的更多信息,但出于技术原因我也愿意。

【问题讨论】:

那么你能猜出为什么循环是'for ...;做 ...;完成'而不是'为了......;做 ...;哦?这样做是有充分理由的 - 但是在其他地方使用了类似 Algol 的反向关键字来标记结尾。 @Jonathan 我不是在争论“为什么会这样而不是那样?” (我想我一开始就说清楚了)。我的问题更像是“丑陋的语法从何而来?” 语法来自 Bourne(以 Bourne shell 闻名)。他曾在 Algol 上工作过,并且非常喜欢它以在 Algol 上对一些 shell 语法进行建模。 Algol 使用反向关键字来标记结构的结尾,因此“case ... esac”是合适的。循环不以 'od' 结尾的原因是 Unix 中已经有一个命令 'od' - 八进制转储。因此,改为使用“完成”。 (而且,众所周知,Bourne shell 源代码是用特殊的 C 语言编写的,带有宏,使它看起来像 Algol。这使得它难以维护。) @Jonathan 好,请回答:) 【参考方案1】:

每个请求:

那么你能猜出为什么循环是'for ...; do ...; done'而不是'for ...; do ...; od'吗?这样做是有充分理由的 - 但在其他地方使用了类似 Algol 的反向关键字来标记结尾。

答案:

语法来自 Bourne(以 Bourne shell 闻名)。他曾在 Algol 上工作过,并且非常喜欢它以在 Algol 上对一些 shell 语法进行建模。 Algol 使用反向关键字来标记结构的结尾,因此“case ... esac”是合适的。循环不以 'od' 结尾的原因是 Unix 中已经有一个命令 'od' - 八进制转储。因此,改为使用“完成”。

众所周知,Bourne shell 源代码是用特殊的 C 语言编写的,带有宏,使其看起来像 Algol。这使得它很难维护。

关于主要问题 - 关于为什么在 case 声明中的替代项周围没有左括号(括号) - 我有几个相关的理论。

首先,在编写 Bourne shell 时(1970 年代后期),很多编辑都是用“ed”,standard text editor 完成的。它没有跳到平衡括号或其他类似符号的概念,因此不需要前导括号。此外,如果您正在编写文档,您可能会使用以下方式整理您的论点:

a) ...blah...
b) ...more...
c) ...again...

左括号通常被省略 - case 语句很适合该模型。

当然,从那时起,我们已经习惯了在您键入右括号时标记匹配的左括号的编辑器,因此旧的 Bourne shell 表示法很麻烦。 POSIX 标准使前导括号是可选的;大多数更现代的类似 POSIX 的 shell(Korn、Bash、Zsh)的实现都将支持这一点,当我不必担心像 Solaris 10 这样 /bin/sh 仍然是忠实 Bourne 的机器的可移植性时,我通常会使用它不允许前导括号的外壳。 (我通常使用#!/bin/ksh 作为shebang。)

【讨论】:

我想说“主要问题”是语法的起源,它是 Algol(在 bash 的***页面上找不到)。无论如何都是好答案:) 有关“原始”Bourne shell 的信息可以在 Sourceforge 的 Heirloom Bourne Shell 项目中找到(以及您最喜欢的搜索引擎可以找到的其他地方)。【参考方案2】:

之所以使用;;,是因为单个;可以在一行中写多个语句,比如:

restart)
   stop; start;;
...

【讨论】:

【参考方案3】:

Bash 可以接受匹配的括号:

case "$1" in
    (start)
        start
        ;;
    (stop)
        stop
        ;;

    etc.

【讨论】:

POSIX 和 Korn shell 也允许前导括号。很久以前,ed('标准文本编辑器' - 在第 7 版 UNIX 手册中是这样说的)没有“反弹光标以匹配左括号”选项,所以左括号不是真的必要的。 Autoconf 和朋友更喜欢在in 之后放置带有适当数量的左括号的评论(例如case "$1" in # (()。这有助于无法处理 $() 中的正常 case 语句的 shell 和编辑器,就像新语法一样,但也适用于原始 Bourne shell。 @Dennis 很高兴知道,但这是在不匹配样式流行之前还是之后? @phunehehe:我不知道你的意思,除非你想知道是否有一个 shell 有 case/esac 不接受左括号。答案是“是”。最初的 Bourne shell 就是一个例子。 不,我的意思是,bash 从一开始就接受匹配的括号吗?还是后来有人抱怨丑陋时,它作为一个功能出现了?【参考方案4】:

右括号有时用于自然语言的列表中,例如

1) do this
2) do that

颠倒的关键字取自某种形式的 Algol,但实际上对于交互式使用来说是一个非常好的主意。它们清楚地划分了构造的结尾,包括 if/else。

例如,使用类似 C 的语法,在解析后:

if (condition)
    command here;

有没有else 来了? rc 是 Plan 9 中的一个 shell,它的语法更类似于 C,它通过提供 if not 而不是 else 来解决这个问题,但它并不漂亮。

使用 Bourne shell 语法,您将拥有 elsefi,并且无需读取额外的输入。

【讨论】:

我明白你关于列表的观点,但其他部分没有多大意义。无论哪种方式,都必须读取附加输入以确定它是else 还是fi 但附加输入是if 的一部分。使用 else 的类 C 语法,如果没有 else,shell 最终会读取下一个命令,这在交互式 shell 中是相当意外的。

以上是关于bash/zsh 脚本中“case”语句的奇怪语法的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本编程学习笔记-case语句

Shell脚本应用(forwhile循环语句和case分支语句)

shell脚本之九:case结构条件句应用实践

shell脚本中case的用法

bash脚本编程选择执行之case语句

Shell编程之case语句与循环语句