为什么 zsh 和 ksh93 选择在模式匹配方面不合规?

为什么 zsh 和 ksh93 选择在模式匹配方面不合规?

模式匹配的 POSIX 文档说:

普通字符是一种与其自身相匹配的模式。它可以是受支持的字符集中的任何字符,除了 NUL、Quoting 中那些需要引用的特殊 shell 字符以及以下三个特殊模式字符。匹配应基于用于对字符进行编码的位模式,而不是基于字符的图形表示。如果引用任何字符(普通字符、shell 特殊字符或模式特殊字符),则该模式应与字符本身匹配。 shell 特殊字符始终需要引号。

据我了解,该模式["!"a]将匹配!和中的任何一个a。这也是我尝试过的大多数 shell 中的行为,除了zshksh93

$ for shell in /bin/*[^c]sh; do
  printf '=%-17s=\n' "$shell"
  "$shell" -c 'case a in ["!"a]) echo 1;; esac'
done
=/bin/ash         =
1
=/bin/bash        =
1
=/bin/dash        =
1
=/bin/heirloom-sh =
1
=/bin/ksh         =
=/bin/lksh        =
1
=/bin/mksh        =
1
=/bin/pdksh       =
1
=/bin/posh        =
1
=/bin/schily-osh  =
1
=/bin/schily-sh   =
1
=/bin/yash        =
1
=/bin/zsh         =

zsh并且ksh93似乎["!"a]与 相同[!a],它匹配除 之外的任何字符a

$ for shell in ksh93 zsh; do
  printf '=%-6s=\n' "$shell"
  "$shell" -c 'case b in ["!"a]) echo 1;; esac'
done
=ksh93 =
1
=zsh   =
1

zsh这样做有什么原因(历史、发展……)吗ksh93


zshksh在仿真和仿真中都做同样的事情sh

busybox sh、Solaris/usr/xpg4/bin/sh和 FreeBSD 的sh行为也类似于 POSIX 文档。


ksh88也像大多数其他 shell 一样,行为在kssh88和之间发生变化ksh93

$ ksh88 -c 'case a in ["!a"]) echo yes; esac'
yes
$ ksh88 -c 'case b in ["a-c"]) echo yes; esac' 
$

答案1

你引用的这段话并不代表你所说的意思。

匹配单个字符的模式

(…) 普通字符是一种与其自身相匹配的模式。 (…) 如果引用任何字符(普通字符、shell 特殊字符或模式特殊字符),则该模式应与字符本身匹配。

所有这些仅适用于在模式中代表自身的字符。这不适用于出现在除预期模式字符之外的上下文中的字符。特别是,它不适用于括号表达式内。括号表达式的语法在 for 条目下描述[

如果一个开括号引入了一个括号表达式,如下所示XBD RE 括号表达式, (…)

!(为了补充,我省略了关于vs 的部分^。)RE 括号表达式的描述没有提及任何有关引用的内容(这并不奇怪,因为它一般是关于括号表达式,而不是关于 shell 脚本中模式中的括号表达式)。

根据 POSIX.1-2008 的严格解释,尚不清楚模式["!"a]应该匹配什么。一种解释是它应该匹配任何字符",!a: 该字符"在方括号表达式中没有特殊含义。我在规范中找不到任何会使这种解释无效的内容。另一种解释是"保留其引用行为,但这意味着括号表达式的内容是!a,并且由于对括号表达式内的引用字符没有特殊处理,因此该集合是 all-but- a。我在 POSIX 规范中找不到任何对您的解释(以及 dash、bash 和其他 shell 的行为)的支持。当然,这是有道理的,但事实并非如此。

POSIX 的未来版本通过添加一些措辞来强制您进行解释是有意义的。例如,描述[可以更改为

如果一个开括号引入了一个括号表达式,如下所示XBD RE 括号表达式,除了在正则表达式表示法中\字符( '!')应取代\字符( '^')在非匹配列表中的作用外,它应引入模式括号表达式,并且任何被引用的字符都应代表其自身作为括号表达式、整理元素或类表达式的元素。以不带引号的 \ 字符开头的括号表达式会产生未指定的结果。否则,'['应匹配字符本身。

鉴于 POSIX 主要是描述性的而不是规范性的,我希望这种破坏 ksh(通常是参考 shell)的更改仅包含在标准的主要更新中,并且现有版本上的任何缺陷至少允许现有的不同解释。

答案2

这是 中的一个错误zsh,在此讨论中报告了该错误[BUG] 括号模式内的引用没有效果:

case b in
  ( ['a-c'] ) echo 'false match' ;;
  ( [a-c] )   echo 'correct match' ;;
esac

将打印false match而不是correct match.

修复计划将于zsh随版本5.3一起发布

答案3

您所阅读的内容仅适用于简单字符。不是 内的字符Bracket expression

其实前面已经说得很清楚了:

当不加引号且位于括号表达式之外时,以下三个字符在模式规范中应具有特殊含义:

? A <问号> ...
* An <星号> ...
[ 如果开括号引入括号表达式 ...

什么你需要阅读 a Bracket Expressionis here

根据规范;在“括号表达式”内,没有引用(对于模式)的概念。

然而,大多数 shell 都会删除任何字符串上的引号,即使该字符串位于“括号表达式”内。这就是为什么 a["!"a]成为[!a]命令的原因。

然而,shell 保留了该字符串的知识曾是大多数 shell 都被引用,因此否定不会生效(与规范中没有在“括号表达式”内引用的概念相反)。

在 ksh 和 zsh 中,该知识不用于评估模式。

为什么会发生这种情况?我相信这些只是错误。


然而,ksh 和 zsh 的行为与大多数 shell 不同。

使用此代码(重复该案例以测试所有 shell 中的所有值):

whichsh="`ps -o pid,args| awk '$1=='"$$"'{print $2}'`"
[ ${whichsh##*/} = zsh  ] && setopt GLOB_SUBST
[ ${whichsh##*/} = zsh4 ] && setopt GLOB_SUBST

a="$1"; printf '%s\t' "testing $a"

case $a in ['!a'])    printf 1 ;; esac
case $a in ["!a"])    printf 2 ;; esac
case $a in ['!'a])    printf 3 ;; esac
case $a in ["!"a])    printf 4 ;; esac
case $a in [\"!\"a])  printf 5 ;; esac
case $a in [!a])      printf 6 ;; esac
printf "\t --"

t1="['!a']";t2='["!a"]';t3="['!'a]";t4='["!"a]';t5='[\"!\"a]'
case $a in $t1)     printf 1 ;; esac
case $a in $t2)     printf 2 ;; esac
case $a in $t3)     printf 3 ;; esac
case $a in $t4)     printf 4 ;; esac
case $a in $t5)     printf 5 ;; esac
case $a in [!a])    printf 6 ;; esac
echo

对于带有“a”的测试,./script.sh a,结果为:

/bin/dash       : testing a     12345    --12345
/bin/sh         : testing a     12345    --12345
/bin/b43sh      : testing a     12345    --12345
/bin/b44sh      : testing a     12345    --12345
/bin/bash       : testing a     12345    --12345
/bin/ksh        : testing a     5        --12345
/bin/ksh93      : testing a     5        --12345
/bin/lksh       : testing a     12345    --12345
/bin/mksh       : testing a     12345    --12345
/bin/zsh        : testing a     5        --12345
/bin/zsh4       : testing a     5        --12345

对“b”进行测试,./script.sh b结果为:

/bin/dash       : testing b     6        --6
/bin/sh         : testing b     6        --6
/bin/b43sh      : testing b     6        --6
/bin/b44sh      : testing b     6        --6
/bin/bash       : testing b     6        --6
/bin/ksh        : testing b     12346    --6
/bin/ksh93      : testing b     12346    --6
/bin/lksh       : testing b     6        --6
/bin/mksh       : testing b     6        --6
/bin/zsh        : testing b     12346    --6
/bin/zsh4       : testing b     12346    --6

当测试模式在变量内有引号时,引号不会被删除并影响结果。当在模式中直接使用引号时, ksh 和 zsh 会删除引号并评估[!a]

所有 shell 应该做的是将引号保留在括号内作为测试字符的一部分。

要获得“引用”的引号,ksh 和 zsh 需要类似 的模式[\"!\"a]。然后两者都匹配a,!"

相关内容