在 Bash 中通配重音文件
Globbing accented files in Bash
我正在尝试验证 Bash
中是否存在文件。我知道文件名(在变量中)但不知道扩展名(可以是 .pmdl
或 .umdl
)。
在 OSX 上有效:
$> ls
ecole.pmdl
$> filename="ecole"
$> ls "$filename."[pu]mdl
ecole.pmdl
但当文件名包含重音符号时则不会:
$> ls
école.pmdl
$> filename="école"
$> ls "$filename."[pu]mdl
ls: école.[pu]mdl: No such file or directory
但是,如果我不使用 globbing,它会起作用:
$> ls "$filename."pmdl
école.pmdl
我正在寻找一种适用于 Linux 和 OSX 的简单解决方案。 This is the closest question 我找到了那个主题。
编辑:
$> bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
编辑 2:
证明场景失败的简短版本(系统地)在 OSX Bash v3.2.57 上使用相同的 é
字符。 Linux Bash 4.3.30 上的相同场景系统地工作(找到)。
$> touch é.txt
$> ls é*
ls: é*: No such file or directory
é != é
$ echo "école." | xxd
00000000: c3a9 636f 6c65 0a ..cole.
$ echo "école." | xxd
00000000: 65cc 8163 6f6c 650a e..cole.
因此我们可以看出它们是不同的字符:
$ echo -e "\x65\xCC\x81"
é
$ echo -e "\xC3\xA9"
é
You are not using the same character in your filename as set in your
variable.
for i in {1..3}; do f="école"; ls "$f."[pu]mdl; echo "$i: $f."[pu]mdl; done
for i in {1..3}; do f="école"; ls "$f."[pu]mdl; echo "$i: $f."[pu]mdl; done
ls: école.[pu]mdl: No such file or directory
1: école.[pu]mdl
ls: école.[pu]mdl: No such file or directory
2: école.[pu]mdl
ls: école.[pu]mdl: No such file or directory
3: école.[pu]mdl
école.pmdl
1: école.[pu]mdl
école.pmdl
2: école.[pu]mdl
école.pmdl
3: école.[pu]mdl
这个错误可能很难重现,因为将字符从一个地方复制粘贴到另一个地方可能会被编辑器翻译,shell,等等,完全改变它。看似相同的角色,却在看似无法区分的细节上有着天壤之别。
这是HFS的要求+here and here (Apple filesystem) to store Unicode strings in decomposed form (as opposed to a pre-composed character).
然后Unicode码位U+0E9的é
这样的字符被分解成两个个字符e
和´
的 Unicode 代码位置分别为 U+065 和 U+0301。
您可以通过创建一个干净的空目录并执行以下操作来查看此差异:
$ a='é'
$ echo "$a" >.text
$ touch "$a"
$ ls > .list
然后比较这两个命令的输出:
$ od -vAn -tx1c .text
c3 a9 0a
303 251 \n
$ od -vAn -tx1c .list
65 cc 81 0a
e 314 201 \n
不相等。
您可以尝试在您的系统中使用此模式:
ls "e$(echo -e '\xcc\x81')cole".[pu]mdl
这只是é
在文件系统中由两个字符表示的表达式。
了解到此问题已在较新的 bash 版本中得到解决。
参考:
How to enter special characters so that bash terminal understands them
tl;dr
任一:使用以下解决方法之一:
ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl
- 最通用,但很麻烦。
ls $'e\x{cc}\x{81}cole'.[pu]mdl
- 很难记住,并且特定于手头的变音符号(尖音,´
)。
ls e?cole.[pu]mdl
- 易于输入和记忆,但仅限于 1 个组合变音符号,可能会产生误报。
或者:通过 Homebrew 安装 Bash 4.3.30 或更高版本 并使用它代替 Bash 3.x macOS 仍然自带:brew install bash
.
血淋淋的细节如下。
相对于 非 ASCII 字符,
macOS 文件系统,HFS+,仅[=235] =] NFD(分解Unicode规范化形式),其中重音字母 由 2 个或更多 Unicode 代码点 表示: 基本字母 ,后跟 组合变音符号(重音符号):
- 在
é
的情况下:
- ASCII基字母-
e
(U+0065
,UTF-8编码0x65
)
- 后跟[=235=]组合尖音符(前一个基本字母上方的
´
,U+0301
, UTF-8 编码 0xcc 0x81
).
- 一些重音字符分解为一个基本字母,后跟 多个 组合变音符号,例如
Ṹ
.
- 请注意,当 创建 文件并匹配文件名 字面意思 时,文件系统接受 NFC 字符串(见下一点),并自动将它们转换为他们的 NFD 等价物(分解它们)。
- 顺便说一句:Linus Torvalds 是 HFS+ 的一般批评家,尤其是它对 NFD 的使用,如 this article.
中所述
通常,但是 - 例如当您在终端或大多数编辑器中键入字符时 - NFC ( composed Unicode规范化形式)被使用,其中(习惯)重音字母表示为1 Unicode 代码点:
- 如
é
:单个Unicode字符U+00E9
,UTF-8编码0xc3 0xa9
.
- NFD 和 NFC 应该 被视为 等效 ,但从 Bash 3.x - 在 macOS 上发现 - 不是:NFC(以及 NFD)输入 原样 当 globbing(在终端中输入或大多数编辑器在 UTF-8 编码脚本中保存)并匹配它 根据文件系统的 NFD 表示逐个代码点,不识别等效的 NFC 和 NFD 表示。
实际上,这意味着 在终端中键入或由大多数编辑器生成的带重音的 NFC 字符与 HFS+ 文件系统中的 NFD 等效字符不匹配。
- 相比之下,指定 literal 文件名 - without globbing - 不受影响:
ls école
,表示为 NFC,确实找到存储在 NFD 中的名为 école
的文件 - 大概是因为 Bash 只是将 NFC 表示传递给 system 函数,该函数确实识别等价。
了解这些 Unicode 正常(规范化)形式 here。
简而言之:Bash 应该 将 NFD 和 NFC 表示视为等效,但从 macOS 10.12 的过时版本开始.1 附带 - Bash 3.2.57.
虽然问题至少从 Bash 4.3.30 when 运行 on macOS 开始得到修复,Apple 还没有t 更新到 Bash 4.x 版本 licensing 原因(参见下面的解决方案)。
查看此 post 的底部,了解 Linux 世界。
有解决方法 用于在 macOS 上使用带重音字符的 globbing 文件名:
[如果可行] 使用Homebrew, 安装最新的4.x Bash 版本 并使用它代替 macOS 附带的那个:brew install bash
.
- 请注意,如果您使用这样的 Bash 版本 (>= 4.3.30),不仅下面描述的其他解决方法不再 必要 ,它们实际上停止工作,因为 Bash 然后仅支持 NFC 输入作为 globbing 模式的一部分(但将其正确映射到文件系统中的 NFD 等效项).
[稳健,但更精细] 使用iconv -t UTF-8-MAC
转换你的Bash 从 NFC 到 NFD 的字符串文字,以便它与文件系统表示相匹配:
ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl
- 或者,也可以使用 ANSI C-quoted string 来表示确切的 NFD UTF-8 字节序列,但晦涩难懂:
ls $'e\x{cc}\x{81}cole'.[pu]mdl
[更简单,但次优]将每个重音字符表示为<base-char>?
,因为从Bash的角度来看,文件系统报告的重音字符等于基本字符 e
后跟 另一个 字符(组合变音符号;相应地调整 multiple 结合变音符号)。
(这种方法显然不是最优的,因为它不会匹配 just é
,而是 any 以 [= 开头的双字符序列16=]):
ls e?cole.[pu]mdl
ext 文件系统 被许多 Linux 发行版 存储文件名 完全符合规定:
换句话说:使用 NFC 名称创建的文件将按原样存储,就像使用 NFD 名称的文件一样。
因此,ext
考虑 NFC 和 NFD 不同的形式,因为它们的字节级表示不同,所以它甚至允许(概念上)相同名称的文件仅在 Unicode 标准形式上不同 - 例如,名为 $'e\xcc\x81cole'
和 $'\xc3\xa9cole'
的文件在由 ls
(école
) 打印时无法区分,但它们是不同的文件 (!).
因此 - 并且适当地 - Linux 上的 Bash 版本不 识别 NFC / NFD 等效,即使版本 >= 4.3.30(与 macOS 不同)。
警告:dash
,它在 Ubuntu 上充当 /bin/sh
,例如,从 Ubuntu 16.04 开始不识别区域设置(多字节字符编码识别),至少在 globbing 时:globbing 符号 ?
匹配单个 byte而不是单个 字符(由活动语言环境的字符编码定义,反映在语言环境类别 LC_CTYPE
中,通常为 UTF-8)。因此,为了匹配单个非ASCII字符,您需要知道该字符的UTF-8编码由多少字节组成,并为每个字节使用?
;例如,NFC é
(2 个字节)必须与 ??
.[1]
匹配
当您在 shebang 行为 #!/bin/sh
的脚本中使用 globbing 时,这可能很重要。
在实践中,很少遇到 NFD 字符串,因此 NFC 字符串既用于创建文件又用于稍后通过 glob 匹配它们,macOS 遇到的不同 Unicode 规范形式的问题很少出现在 Linux。
[1] dash
旨在成为一个快速的、POSIX 兼容的 shell 实现(主要是 受限 到 POSIX 特征),但在这种情况下它似乎不足:part of the POSIX spec. describing the pattern-matching notation 清楚地谈论 个字符 ,而不是 个字节 : A <question-mark> is a pattern that shall match any character.
Character Sets.
部分描述了对多字节字符编码的支持
我正在尝试验证 Bash
中是否存在文件。我知道文件名(在变量中)但不知道扩展名(可以是 .pmdl
或 .umdl
)。
在 OSX 上有效:
$> ls
ecole.pmdl
$> filename="ecole"
$> ls "$filename."[pu]mdl
ecole.pmdl
但当文件名包含重音符号时则不会:
$> ls
école.pmdl
$> filename="école"
$> ls "$filename."[pu]mdl
ls: école.[pu]mdl: No such file or directory
但是,如果我不使用 globbing,它会起作用:
$> ls "$filename."pmdl
école.pmdl
我正在寻找一种适用于 Linux 和 OSX 的简单解决方案。 This is the closest question 我找到了那个主题。
编辑:
$> bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
编辑 2:
证明场景失败的简短版本(系统地)在 OSX Bash v3.2.57 上使用相同的 é
字符。 Linux Bash 4.3.30 上的相同场景系统地工作(找到)。
$> touch é.txt
$> ls é*
ls: é*: No such file or directory
é != é
$ echo "école." | xxd
00000000: c3a9 636f 6c65 0a ..cole.
$ echo "école." | xxd
00000000: 65cc 8163 6f6c 650a e..cole.
因此我们可以看出它们是不同的字符:
$ echo -e "\x65\xCC\x81"
é
$ echo -e "\xC3\xA9"
é
You are not using the same character in your filename as set in your variable.
for i in {1..3}; do f="école"; ls "$f."[pu]mdl; echo "$i: $f."[pu]mdl; done
for i in {1..3}; do f="école"; ls "$f."[pu]mdl; echo "$i: $f."[pu]mdl; done
ls: école.[pu]mdl: No such file or directory
1: école.[pu]mdl
ls: école.[pu]mdl: No such file or directory
2: école.[pu]mdl
ls: école.[pu]mdl: No such file or directory
3: école.[pu]mdl
école.pmdl
1: école.[pu]mdl
école.pmdl
2: école.[pu]mdl
école.pmdl
3: école.[pu]mdl
这个错误可能很难重现,因为将字符从一个地方复制粘贴到另一个地方可能会被编辑器翻译,shell,等等,完全改变它。看似相同的角色,却在看似无法区分的细节上有着天壤之别。
这是HFS的要求+here and here (Apple filesystem) to store Unicode strings in decomposed form (as opposed to a pre-composed character).
然后Unicode码位U+0E9的é
这样的字符被分解成两个个字符e
和´
的 Unicode 代码位置分别为 U+065 和 U+0301。
您可以通过创建一个干净的空目录并执行以下操作来查看此差异:
$ a='é'
$ echo "$a" >.text
$ touch "$a"
$ ls > .list
然后比较这两个命令的输出:
$ od -vAn -tx1c .text
c3 a9 0a
303 251 \n
$ od -vAn -tx1c .list
65 cc 81 0a
e 314 201 \n
不相等。
您可以尝试在您的系统中使用此模式:
ls "e$(echo -e '\xcc\x81')cole".[pu]mdl
这只是é
在文件系统中由两个字符表示的表达式。
了解到此问题已在较新的 bash 版本中得到解决。
参考:
How to enter special characters so that bash terminal understands them
tl;dr
任一:使用以下解决方法之一:
ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl
- 最通用,但很麻烦。ls $'e\x{cc}\x{81}cole'.[pu]mdl
- 很难记住,并且特定于手头的变音符号(尖音,´
)。ls e?cole.[pu]mdl
- 易于输入和记忆,但仅限于 1 个组合变音符号,可能会产生误报。
或者:通过 Homebrew 安装 Bash 4.3.30 或更高版本 并使用它代替 Bash 3.x macOS 仍然自带:
brew install bash
.
血淋淋的细节如下。
相对于 非 ASCII 字符,
macOS 文件系统,HFS+,仅[=235] =] NFD(分解Unicode规范化形式),其中重音字母 由 2 个或更多 Unicode 代码点 表示: 基本字母 ,后跟 组合变音符号(重音符号):
- 在
é
的情况下:- ASCII基字母-
e
(U+0065
,UTF-8编码0x65
) - 后跟[=235=]组合尖音符(前一个基本字母上方的
´
,U+0301
, UTF-8 编码0xcc 0x81
).
- ASCII基字母-
- 一些重音字符分解为一个基本字母,后跟 多个 组合变音符号,例如
Ṹ
. - 请注意,当 创建 文件并匹配文件名 字面意思 时,文件系统接受 NFC 字符串(见下一点),并自动将它们转换为他们的 NFD 等价物(分解它们)。
- 顺便说一句:Linus Torvalds 是 HFS+ 的一般批评家,尤其是它对 NFD 的使用,如 this article. 中所述
- 在
通常,但是 - 例如当您在终端或大多数编辑器中键入字符时 - NFC ( composed Unicode规范化形式)被使用,其中(习惯)重音字母表示为1 Unicode 代码点:
- 如
é
:单个Unicode字符U+00E9
,UTF-8编码0xc3 0xa9
. - NFD 和 NFC 应该 被视为 等效 ,但从 Bash 3.x - 在 macOS 上发现 - 不是:NFC(以及 NFD)输入 原样 当 globbing(在终端中输入或大多数编辑器在 UTF-8 编码脚本中保存)并匹配它 根据文件系统的 NFD 表示逐个代码点,不识别等效的 NFC 和 NFD 表示。
实际上,这意味着 在终端中键入或由大多数编辑器生成的带重音的 NFC 字符与 HFS+ 文件系统中的 NFD 等效字符不匹配。 - 相比之下,指定 literal 文件名 - without globbing - 不受影响:
ls école
,表示为 NFC,确实找到存储在 NFD 中的名为école
的文件 - 大概是因为 Bash 只是将 NFC 表示传递给 system 函数,该函数确实识别等价。
- 如
了解这些 Unicode 正常(规范化)形式 here。
简而言之:Bash 应该 将 NFD 和 NFC 表示视为等效,但从 macOS 10.12 的过时版本开始.1 附带 - Bash 3.2.57.
虽然问题至少从 Bash 4.3.30 when 运行 on macOS 开始得到修复,Apple 还没有t 更新到 Bash 4.x 版本 licensing 原因(参见下面的解决方案)。
查看此 post 的底部,了解 Linux 世界。
有解决方法 用于在 macOS 上使用带重音字符的 globbing 文件名:
[如果可行] 使用Homebrew, 安装最新的4.x Bash 版本 并使用它代替 macOS 附带的那个:
brew install bash
.- 请注意,如果您使用这样的 Bash 版本 (>= 4.3.30),不仅下面描述的其他解决方法不再 必要 ,它们实际上停止工作,因为 Bash 然后仅支持 NFC 输入作为 globbing 模式的一部分(但将其正确映射到文件系统中的 NFD 等效项).
[稳健,但更精细] 使用
iconv -t UTF-8-MAC
转换你的Bash 从 NFC 到 NFD 的字符串文字,以便它与文件系统表示相匹配:
ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl
- 或者,也可以使用 ANSI C-quoted string 来表示确切的 NFD UTF-8 字节序列,但晦涩难懂:
ls $'e\x{cc}\x{81}cole'.[pu]mdl
- 或者,也可以使用 ANSI C-quoted string 来表示确切的 NFD UTF-8 字节序列,但晦涩难懂:
[更简单,但次优]将每个重音字符表示为
<base-char>?
,因为从Bash的角度来看,文件系统报告的重音字符等于基本字符e
后跟 另一个 字符(组合变音符号;相应地调整 multiple 结合变音符号)。 (这种方法显然不是最优的,因为它不会匹配 justé
,而是 any 以 [= 开头的双字符序列16=]):
ls e?cole.[pu]mdl
ext 文件系统 被许多 Linux 发行版 存储文件名 完全符合规定:
换句话说:使用 NFC 名称创建的文件将按原样存储,就像使用 NFD 名称的文件一样。
因此,ext
考虑 NFC 和 NFD 不同的形式,因为它们的字节级表示不同,所以它甚至允许(概念上)相同名称的文件仅在 Unicode 标准形式上不同 - 例如,名为 $'e\xcc\x81cole'
和 $'\xc3\xa9cole'
的文件在由 ls
(école
) 打印时无法区分,但它们是不同的文件 (!).
因此 - 并且适当地 - Linux 上的 Bash 版本不 识别 NFC / NFD 等效,即使版本 >= 4.3.30(与 macOS 不同)。
警告:dash
,它在 Ubuntu 上充当 /bin/sh
,例如,从 Ubuntu 16.04 开始不识别区域设置(多字节字符编码识别),至少在 globbing 时:globbing 符号 ?
匹配单个 byte而不是单个 字符(由活动语言环境的字符编码定义,反映在语言环境类别 LC_CTYPE
中,通常为 UTF-8)。因此,为了匹配单个非ASCII字符,您需要知道该字符的UTF-8编码由多少字节组成,并为每个字节使用?
;例如,NFC é
(2 个字节)必须与 ??
.[1]
当您在 shebang 行为 #!/bin/sh
的脚本中使用 globbing 时,这可能很重要。
在实践中,很少遇到 NFD 字符串,因此 NFC 字符串既用于创建文件又用于稍后通过 glob 匹配它们,macOS 遇到的不同 Unicode 规范形式的问题很少出现在 Linux。
[1] dash
旨在成为一个快速的、POSIX 兼容的 shell 实现(主要是 受限 到 POSIX 特征),但在这种情况下它似乎不足:part of the POSIX spec. describing the pattern-matching notation 清楚地谈论 个字符 ,而不是 个字节 : A <question-mark> is a pattern that shall match any character.
Character Sets.