为什么 Android 的 "not the following character" 正则表达式是 [!x] 而不是 [^x]?

Why "not the following character" regex for Android is [!x] and not [^x]?

我需要编写一个 bash 脚本,它将在 Android 设备上执行。除此之外,我需要这个脚本来计算字符串中特定字符的出现次数,因为 wc(字数统计)实用程序在 Android shell 中不可用,我这样做就像这个:

my_string="oneX two threeX"; x_amount="${my_string//[^X]}"; echo $x_amount; echo "${#x_amount}"

当我在桌面上运行上述命令时,它returns(正如我所料):

XX
2

但是如果我在我的 Android 设备上执行相同的命令(通过 adb shell),结果令我惊讶的是:

one two three
13

我发现(只是猜测)如果我用 ! 代替 ^,那么命令就变成了:

my_string="oneX two threeX"; x_amount="${my_string//[!X]}"; echo $x_amount; echo "${#x_amount}";

然后,在 Android 上,它产生了我期望的结果:

XX
2

虽然同一命令在桌面上失败并显示以下消息:

event not found: X]

虽然我已经想出了如何"make it work"我想了解以下几点:

  1. 除了Androidshell还有什么地方用了[!X]表示法,而不是[^X]?

  2. 这种写法有什么特殊的名称吗?

  3. 是否有任何特定原因 Android 不支持 [^X]

P.S.: 我需要 运行 脚本的设备有一个相当旧的 Android (4.4) 版本,所以这个 'issue' 可能是 Android 版本特定的,即使是这种情况,上述问题仍然存在。

Android 的 shell 是 mksh, 使用与 Bash.

不同的 RegEx 或模式方言

见: File name patterns in mksh's man-page:

    File name patterns
...
     [!...]  Like [...], except it matches any octet not inside the brackets.

让我们测试一些 shell 与字符串替换和否定字符 class 模式 [!...] 语法的兼容性:

#!/usr/bin/env bash

shells=( ash bash dash ksh93 mksh tcsh zsh )
compat=()
not_compat=()
for shell in "${shells[@]}"; do
  if [ "$(
    "$shell" <<'EOF' 2>/dev/null
my_string="oneX two threeX"
x_amount="${my_string//[!X]}"; echo "$x_amount${#x_amount}"
EOF
  )" = "XX2" ]; then
    compat+=("$shell")
  else
    not_compat+=("$shell")
  fi
done
echo "Shells that understands the [!...] negative class syntax:"
printf '%s\n' "${compat[@]}"
echo
echo "Shells that don't understand string substitution:"
printf '%s\n' "${not_compat[@]}"

输出:

Shells that understands the [!...] negative class syntax:
bash
ksh93
mksh
zsh

Shells that don't understand string substitution:
ash
dash
tcsh

另请注意,即使禁用其 Gnu 扩展,sed 也无法理解 POSIX 负字符组表示法 [!...]

sed --posix 's/[!X]//g' <<<'oneX two threeX'
one two three

但是

sed --posix 's/[^X]//g' <<<'oneX two threeX'
XX

第一:模式匹配有很多不同的表示法; shell 在这里使用的不是正则表达式,它是 "glob" (or "wildcard") pattern — similar to an RE in some ways, very different in others (就像“*”的意思)。这些基本模式类型也有变体,包括 glob 语法的不同变体(尤其是 bash 的 "extended glob" 语法),以及正则表达式语法的许多变体("basic" RE,"extended" RE, Perl 兼容的 RE, etc etc etc...).

了解您正在使用的工具采用的语法并适当地调整您的模式通常很重要。

现在,对于否定括号表达式的情况,POSIX standard from 2004 是这样说的:

The description of basic regular expression bracket expressions in the Base Definitions volume of IEEE Std 1003.1-2001, Section 9.3.5, RE Bracket Expression shall also apply to the pattern bracket expression, except that the exclamation mark character ( '!' ) shall replace the circumflex character ( '^' ) in its role in a "non-matching list" in the regular expression notation. A bracket expression starting with an unquoted circumflex character produces unspecified results.

2018 version 类似,但有点乱码;不确定那里发生了什么。)

所以,!实际上是这里接受的标准。但是 bash and zsh 都使用 ! 来引入历史扩展,所以显然已经决定最好也接受 ^ 以避免与历史机制发生冲突。

bash 接受 "${my_string//[^X]}""${my_string//[!X]}",但 zsh 将后者误认为是试图引用包含 X] 的较早命令,给出了您看到的错误.