普通字符是一种与其自身相匹配的模式。它可以是受支持的字符集中的任何字符,除了 NUL、Quoting 中那些需要引用的特殊 shell 字符以及以下三个特殊模式字符。匹配应基于用于对字符进行编码的位模式,而不是基于字符的图形表示。如果引用任何字符(普通字符、shell 特殊字符或模式特殊字符),则该模式应与字符本身匹配。 shell 特殊字符始终需要引号。
据我了解,该模式["!"a]
将匹配!
和中的任何一个a
。这也是我尝试过的大多数 shell 中的行为,除了zsh
和ksh93
:
$ 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
?
zsh
ksh
在仿真和仿真中都做同样的事情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 Expression
is 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
,!
和"
。