簡體   English   中英

在 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

但是,如果我不使用通配符,它​​會起作用:

$> ls "$filename."pmdl
école.pmdl

我正在尋找一個適用於 Linux 和 OSX 的簡單解決方案。 這是我在該主題上找到的最接近的問題

編輯:

$> 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 上使用相同的é char 證明該場景(系統地)失敗的簡短版本。 Linux Bash 4.3.30 上的相同場景系統地工作(找到)。

$> touch é.txt
$> ls é*
ls: é*: No such file or directory

HFS+ here and here (Apple 文件系統)要求以分解形式存儲 Unicode 字符串(而不是預先組合的字符)。

然后將Unicode碼位U+0E9的é這樣的字符分解為Unicode碼位U+065和U+0301的兩個字符e´

您可以通過創建一個干凈的空目錄並執行以下操作來查看此差異:

$ 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 版本中解決。

參考:

如何輸入特殊字符以便 bash 終端理解它們

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基本字母- e ( U+0065 , UTF-8 編碼0x65 )
      • 后跟組合重音符號(前面的基本字母上方的´ U+0301 ,UTF-8 編碼0xcc 0x81 )。
    • 一些重音字符分解為一個基本字母,后跟多個組合變音符號,例如的情況。
    • 請注意,文件系統在創建文件和逐字匹配文件名時接受 NFC 字符串(參見下一點),並自動將它們轉換為它們的 NFD 等效項(分解它們)。
    • 順便說一句:對 HFS+ 尤其是它對 NFD 的使用的著名批評者是 Linus Torvalds,如本文所述
  • 然而,通常- 例如當您在終端或大多數編輯器中鍵入字符時 - 使用NFC組合Unicode 規范化形式),其中(習慣的)帶重音的字母1 個Unicode 代碼點表示:

    • é的情況下:單個Unicode 字符U+00E9 ,UTF-8 編碼0xc3 0xa9
    • NFD和NFC視為等效的,但作為擊3.X-在MacOS如發現-不是:NFC(和也NFD)輸入取為-被通配(無論是作為輸入在終端或作為保存大多數編輯器使用 UTF-8 編碼腳本)並將其逐個碼點與文件系統的 NFD 表示進行匹配,而不識別等效的 NFC 和 NFD 表示。
      實際上,這意味着在終端中輸入或由大多數編輯器生成的帶重音的 NFC 字符與其在 HFS+ 文件系統中的 NFD 等效字符不匹配
    • 相比之下,指定文字文件名 -不使用通配符- 不受影響: ls école ,表示為 NFC,確實找到了名為école的文件,該文件存儲在 NFD 中 - 大概是因為 Bash 只是將 NFC 表示傳遞給了一個系統函數,該函數可以識別等價性。

在此處閱讀這些Unicode 標准(規范化)形式

簡而言之:從 macOS 10.12.1 附帶的過時版本 - Bash 3.2.57 開始,Bash應該將 NFD 和 NFC 表示識別為等效的,但不會。

雖然在 macOS 上運行時該問題至少從 Bash 4.3.30 開始得到解決,但出於許可原因, Apple 並未更新到 Bash 4.x版本(請參閱下文以獲取解決方案)。

請參閱本文底部以了解Linux世界。


有在 macOS 上使用重音字符通配文件名的解決方法

  • [如果可行] 使用Homebrew安裝最新的 4.x Bash 版本並使用它而不是 macOS 附帶的版本: brew install bash

    • 請注意,如果您使用這樣的 Bash 版本 (>= 4.3.30),不僅不再需要下面描述的其他解決方法,它們實際上會停止工作,因為 Bash 僅支持NFC輸入作為通配模式的一部分(但映射它正確到文件系統中的 NFD 等效項)。
  • [健壯,但更詳細]使用iconv -t UTF-8-MAC將您的 Bash 字符串文字從 NFC 轉換為 NFD,使其與文件系統表示相匹配:
    ls "$(iconv -t UTF-8-MAC <<<'école')."[pu]mdl

    • 或者,也可以使用ANSI C 引用的字符串來表示確切的 NFD UTF-8 字節序列,但既晦澀又麻煩:
      ls $'e\\x{cc}\\x{81}cole'.[pu]mdl
  • [更簡單,但次優] 將每個重音字符表示為<base-char>? ,因為從 Bash 的角度來看,文件系統報告的重音字符等於基本字符e后跟另一個字符(組合變音符號;針對多個組合變音符號進行相應調整)。 (這種做法顯然是不理想的,因為它不會匹配é ,但任何兩個字符序列開始e ):
    ls e?cole.[pu]mdl


許多Linux發行版使用的ext文件系統完全按照指定的方式存儲文件名

換句話說:使用 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 symbol ? 匹配單個字節而不是單個字符(由活動語言環境的字符編碼定義,反映在語言環境類別LC_CTYPE ,通常是 UTF-8)。 因此,為了匹配單個非 ASCII 字符,您需要知道該字符的 UTF-8 編碼由多少個字節組成,並使用? 對於每個字節; 例如,NFC é (2 個字節)必須與?? . [1]

當您在 shebang 行為#!/bin/sh腳本中使用通配符時,這可能很重要。

在實踐中,NFD 字符串很少遇到,因此使用 NFC 字符串創建文件並稍后通過 glob 匹配它們,macOS 遇到的不同 Unicode 范式的問題很少在 Linux 上出現。


[1] dash旨在成為一個快速的、符合 POSIX 標准的 shell 實現(主要限於POSIX 功能),但在這種情況下它似乎達不到: POSIX 規范一部分。 描述模式匹配符號清楚地談論字符,而不是字節A <question-mark> is a pattern that shall match any character.
字符集部分描述了對多字節字符編碼的支持。

é != é

$ 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 等轉換,從而完全改變它。 它可能看起來像同一個角色,但由於看似無法區分的細節,它確實有所不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM