Bash中的重音文件

时间:2016-10-15 18:00:43

标签: bash macos glob

我试图验证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和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

3 个答案:

答案 0 :(得分:2)

要求HFS + herehere(Apple文件系统)以分解形式存储Unicode字符串(而不是pre-composed character)。

然后,Unicode代码位置U + 0E9的é之类的字符被分解为Unicode代码位置的两个字符e´分别为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

答案 1 :(得分:2)

<强> TL;博士

  • 使用以下解决方法之一

    • 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或更高版本并使用它而不是macOS仍然附带的Bash 3.x:brew install bash

下面的血腥细节。

关于非ASCII字符

  • macOS文件系统 HFS+ ,只说 NFD 分解 Unicode规范化表单),其中重音字母 2个或更多 Unicode代码点表示:基础字母,然后是组合变音符号(重音符号):

    • 如果是é
      • ASCII 基础字母 - eU+0065,UTF-8编码0x65
      • 后跟 组合急性重音(前一个基本字母´之上的U+0301,UTF-8编码{{1} })。
    • 某些重音字符会分解为基本字母,然后是多个组合变音符号,例如0xcc 0x81
    • 请注意,当创建文件并匹配文件名字面时,文件系统会接受NFC字符串(请参阅下一点),并自动将它们转换为NFD等效项(分解它们)。
    • 顺便说一句:一般来说,HFS +的一个值得注意的批评者尤其是对NFD的使用是Linus Torvalds,如this article所示。
  • 通常 - 例如,当您在终端或大多数编辑中键入字符时 - NFC 撰写使用 Unicode规范化表单),其中(通常)重音字母 1 Unicode代码点表示:

    • 如果是单个 Unicode字符é,则UTF-8编码U+00E9
    • NFD和NFC 视为等效 ,但从 Bash 3.x - 在macOS上找到 - 不是& #39; t 通配 时,NFC(以及NFD)输入 as <(在终端或由大多数编辑人员以UTF-8编码的脚本保存,并将 codepoint by codepoint 与文件系统的NFD表示相匹配,而不识别等效的NFC和NFD表示。
      实际上,这意味着在终端中输入的重音的NFC字符或大多数编辑器生成的NFC字符与HFS +文件系统中的NFD等价物不匹配
    • 相比之下,指定文字文件名 - 没有通配 - 不受影响:0xc3 0xa9,表示为NFC,确实找到名为{{1}的文件大概存储在NFD中,因为Bash只是将NFC表示传递给 system 函数,该函数确实识别了等价。

了解这些 Unicode正常(规范化)表单 here

简而言之: Bash 应该认识到NFD和NFC表示是等效的,但不是,就像macOS 10.12.1附带的过时版本一样 - Bash 3.2.57 。

虽然至少在BOS 4.3.30 上运行macOS 时问题已解决,但 Apple尚未更新为Bash 4.x 版本许可原因(请参阅下面的解决方案)。

请参阅本文的底部,了解 Linux 世界。

对于在macOS上带有重音字符的通配文件名,有变通方法

  • [如果可行]使用Homebrew安装最新的4.x Bash版本并使用它而不是macOS附带的版本:ls école

    • 请注意,如果您使用这样的Bash版本(&gt; = 4.3.30),不仅下面描述的其他解决方法不再是必需,它们实际上停止工作,因为Bash只支持 NFC 输入作为通配模式的一部分(但在文件系统中正确映射到NFD等效项)。
  • [健壮,但更精细] 使用école 将Bash字符串文字从NFC转换为NFD,以便与文件系统表示相匹配:
    brew install bash

    • 或者,使用ANSI C-quoted string来表示精确的NFD UTF-8字节序列是可能的,但是模糊不清且很麻烦:
      iconv -t UTF-8-MAC
  • [更简单,但次优]将每个​​重音字符表示为ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl,因为从Bash的角度来看,由文件系统报告的重音字符,等于基本字符ls $'e\x{cc}\x{81}cole'.[pu]mdl后跟另一个字符(组合变音符号;相应地调整多个组合变音符号)。 (这种方法显然不是最理想的,因为它不会匹配只是 <base-char>?,而是任何e开头的双字符序列:
    é

许多 Linux 发行版使用的 ext文件系统完全按指定的方式存储文件名 :< / p>

换句话说:使用NFC名称创建的文件存储为具有NFD名称的文件。

因此,e考虑NFC和NFD的不同形式,因为它们的字节级表示不同,因此它甚至允许(概念上)相同名称的文件仅在Unicode正常形式上不同 - 例如,名为的文件由ls e?cole.[pu]mdlext)打印时,$'e\xcc\x81cole'$'\xc3\xa9cole'无法区分,但它们是不同的文件(!)。

因此 - 并且适当地 - Linux 上的Bash版本 / p>

警告ls,例如,在Ubuntu上充当école,从Ubuntu 16.04起,不支持区域设置(可识别多字节字符编码) ,至少在 globbing 时:globbing symbol dash匹配单个字节而不是单个字符(由活动区域设置定义& #39;字符编码,反映在语言环境类别/bin/sh中,通常为UTF-8)。因此,为了匹配单个非ASCII字符,您需要知道该字符的UTF-8编码组成的字节数,并为每个字节使用?;例如,NFC LC_CTYPE(2个字节)必须与?匹配。 [1]

当你在shebang行为é的脚本中使用globbing时,这可能很重要。

在实践中,很少遇到NFD字符串,因此使用NFC字符串创建文件并稍后通过globs匹配它们时,macOS体验的不同Unicode正常形式的问题很少出现在Linux上。

[1] ??旨在成为一个快速的,符合POSIX标准的shell实现(主要是限制到POSIX功能),但在这种情况下它似乎下降简短:part of the POSIX spec. describing the pattern-matching notation清楚地谈到字符,而非字节#!/bin/sh
Character Sets

部分介绍了对多字节字符编码的支持

答案 2 :(得分:1)

é!=é

$ 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"
é
  

您的文件名中没有使用与您的文件名相同的字符   变量

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等完全改变它来翻译。它可能看起来像是相同的角色,但它看似难以区分的细节真的不同。