搜索并替换组合的 Unicode 字符

搜索并替换组合的 Unicode 字符

我在 Debian 机器上有一个深层文件夹结构,其中目录名和文件名包含一些“特殊”字符(äöü)。但是,这些不是“ISO-8859-1”,而是 Unicode 组合字符形式。据我所知,这是一个简单的a 加上两个点(分音符/元音变音)作为“单独”字符。

我尝试使用 find 和 sed 批量重命名所有文件和文件夹:

#!/bin/bash

# Files - normal characters
find . -depth -name "*[äöüÄÖÜ]*" -exec bash -c 'mv "$1" "$(echo $1 | sed -e "s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g")"' _ {} \;
# Files - Unicode combining characters
find . -depth -name "*[äöüÄÖÜ]*" -exec bash -c 'mv "$1" "$(echo $1 | sed -e "s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g")"' _ {} \;

# Directories - normal characters
find . -depth -type d -name "*[äöüÄÖÜ]*" -exec bash -c 'mv "$1" "$(echo $1 | sed -e "s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g")"' _ {} \;
# Directories - Unicode combining characters
find . -depth -type d -name "*[äöüÄÖÜ]*" -exec bash -c 'mv "$1" "$(echo $1 | sed -e "s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g")"' _ {} \;

但是,如果参数中的参数是组合 Unicode 而不是 ISO-8859-1,则似乎还会选择 文件/文件夹名称中find出现的所有内容。例如,aä-nameä

$ find . -name "*[ä]*"  //<-- one letter ä
./filename_one_letter_ä
$ find . -name "*[ä]*"  //<-- combining letter ä
./filename_with_just_a
./filename_one_letter_ä
./filename_with_combining_diaeresis_ä

因此,它 sed会原封不动地传递文件名,因此 mv当我要求它重命名时会抱怨,例如,将“ Baustand”重命名为“ Baustand”(即源和目标相同)。

如果无法使用 find 进行搜索,如何在 Linux 系统上的文件/文件夹结构中搜索和替换所有组合的 Unicode äöüÄ、 、ÖÜ我可以尝试另一种方法吗?

我的文件和目录名称的示例是:

/Projekte/03-11_Törggel_Mammern/Baustand/03-11_Törggel-Baustand_190501_0009.jpg

我想重命名为

/Projekte/03-11_Toerggel_Mammern/Baustand/03-11_Toerggel-Baustand_190501_0009.jpg

的输出echo $LANGen_US.UTF-8.

答案1

我无法重现这个(甚至测试它)ATM,但是......

嗯,你知道[xyz]匹配x 或者 y 或者 z。我想,当你说[äöü…](使用组合字符)时,它正在看到

  • a
  • (结合) ¨
  • o
  • (结合) ¨
  • u
  • (结合) ¨
  • ……

因此它会找到名称包括a, o,的文件u 或者(组合)  ¨,但不一定或 

所以尝试分别寻找它们:

find . -depth -name "*ä*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/ä/ae/g")"' _ {} ';'
find . -depth -name "*ö*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/ö/oe/g")"' _ {} ';'
find . -depth -name "*ü*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/ü/ue/g")"' _ {} ';'
find . -depth -name "*Ä*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/Ä/Ae/g")"' _ {} ';'
find . -depth -name "*Ö*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/Ö/Oe/g")"' _ {} ';'
find . -depth -name "*Ü*" -exec bash -c 'mv "$1" "$(echo "$1" | sed -e "s/Ü/Ue/g")"' _ {} ';'

(不带方括号)。请注意,您的echo $1(不带引号)对于某些文件名可能会失败。 (';'相当于\;;我更喜欢在风格上避免反斜杠。)

或者,如果你真的真的想要用一个命令完成这一切,请尝试

find . -depth "(" -name "*ä*" -o -name "*ö*" -o -name "*ü*"     \
               -o -name "*Ä*" -o -name "*Ö*" -o -name "*Ü*" ")" \
       -exec bash -c 'mv "$1" "$(printf "%s" "$1" | sed -e "s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g")"' _ {} ';'

(printf "%s"在功能上与 非常相似echo,但更安全。在风格上,我通常会使用printf '%s'(带单引号);我在这里使用双引号,因为我们位于单引号 ( 'mv …') 字符串内。)

有可能

… "(" -iname "*ä*" -o -iname "*ö*" -o -iname "*ü*" ")" …

将工作。


LANG另外,如果您设置为 ,您最初尝试的方法可能会起作用de_DE.UTF-8

答案2

de-ASCII音译uconv做你想做的事。例如,字符的预组合和分解以及大写和小写版本ä

$ printf '\u00c4 \u00e4 A\u0308 a\u0308\n'
Ä ä Ä ä
$ printf '\u00c4\u00e4A\u0308a\u0308' | uconv -x name
\N{LATIN CAPITAL LETTER A WITH DIAERESIS}\N{LATIN SMALL LETTER A WITH DIAERESIS}\N{LATIN CAPITAL LETTER A}\N{COMBINING DIAERESIS}\N{LATIN SMALL LETTER A}\N{COMBINING DIAERESIS}
$ printf '\u00c4\u00e4A\u0308a\u0308 \u00c4 A\u0308 \u00c4B\n' | uconv -x de-ASCII
AeaeAeae AE AE AEB

(另请注意根据上下文Ä更改为“AE或” )。Ae

因此,在这里,您可以尝试使用类似的内容(在 zsh 中)转换名称中包含非 ASCII 字符的所有文件:

autoload zmv
zmv -n $'(**/)(*[^\1-\177]*)' '$1$(print -rn -- $2 | uconv -x de-ASCII)'

例子:

$ touch $'\u00c4\u00e4A\u0308a\u0308'
$ touch $'St\ue9phane' $'Ste\u301phane'
$ zmv -n $'(**/)(*[^\1-\177]*)' '$1$(print -rn -- $2 | uconv -x de-ASCII)'
mv -- ÄäÄä AeaeAeae
mv -- Stéphane Stephane
mv -- Stéphane Stephane

如果满意,请删除-n(试运行)。

或者避免uconv每个文件运行一个,以防万一您有数百万个文件需要重命名。

files=(**/*[^$'\1-\177']*)
typeset -U basenames=($files:t)
typeset -A translation
print -rNC1 -- $basenames | uconv -x de-ASCII |
  for name in $basenames; do
    IFS= read -rd '' translated && translation[$name]=$translated
  done

zmv -n $'(**/)(*[^\1-\177]*)' '$1${translation[$2]-$2}'

作为如何在分解形式中查找包含分音符字符的文件的更一般问题的答案,您只需要查找组合分音符字符 (U+308)。

所以:

find . -name $'*\u0308*'

或者如果它必须只跟随 AOUaou 之一:

find . -name $'*[AOUaou]\u0308*'

对于预组合形式的那些,您需要单独列出它们:

find . -name '*[ÄËÏÖÜäëïöüÿŸǕǖǗǘǙǚǛǜǞǟȪȫ΅ΐΪΫΰϊϋϔӒӓӚӛӜӝӞӟӤӥӦӧӪӫӬӭӰӱӴӵӸӹḦḧḮḯṎṏṲṳṺṻẄẅẌẍẗ⍡⍢⍣⍤⍥⍨⍩⸚]*'

或者:

find . -name $'*[\uA8\uC4\uCB\uCF\uD6\uDC\uE4\uEB\uEF\uF6\uFC\uFF\u178\u1D5\u1D6\u1D7\u1D8\u1D9\u1DA\u1DB\u1DC\u1DE\u1DF\u22A\u22B\u385\u390\u3AA\u3AB\u3B0\u3CA\u3CB\u3D4\u4D2\u4D3\u4DA\u4DB\u4DC\u4DD\u4DE\u4DF\u4E4\u4E5\u4E6\u4E7\u4EA\u4EB\u4EC\u4ED\u4F0\u4F1\u4F4\u4F5\u4F8\u4F9\u1E26\u1E27\u1E2E\u1E2F\u1E4E\u1E4F\u1E72\u1E73\u1E7A\u1E7B\u1E84\u1E85\u1E8C\u1E8D\u1E97\u2361\u2362\u2363\u2364\u2365\u2368\u2369\u2E1A]*'

$'\uXXXX'现在包括 bash 在内的一些其他 shell 支持使用 zsh 的表示法。

相关内容