非 ASCII 字符
ASCII 字符代码范围从 0x00 到 0x7F 十六进制。因此,任何代码大于0x7F 的字符都是非ASCII 字符。这包括 UTF-8 中的大部分字符(ASCII 代码本质上是 UTF-8 的子集)。例如,日语字符
あ
在 UTF-8 中以十六进制编码为
E3 81 82
UTF-8 一直是 Red Hat Linux since version 8.0 (2002), SuSE Linux since version 9.1 (2004), and Ubuntu Linux since version 5.04 (2005) 等的默认字符编码。
ASCII 控制字符
在 ASCII 码中,0x00 到 0x1F 和 0x7F 表示控制字符,例如 ESC (0x1B)。这些控制字符最初并不是可打印的,尽管其中一些字符(例如换行符 0x0A)可以被解释和显示。
在我的系统上,ls 默认将所有控制字符显示为?,除非我通过--show-control-chars 选项。我猜您要删除的文件包含 ASCII 控制字符,而不是非 ASCII 字符。这是一个重要的区别:如果您删除包含非 ASCII 字符的文件名,您可能会删除恰好以另一种语言命名的合法文件。
字符代码的正则表达式
POSIX
POSIX 提供了一个非常方便的字符类集合来处理这些类型的字符(感谢bashophil 指出这一点):
[:cntrl:] Control characters
[:graph:] Graphic printable characters (same as [:print:] minus the space character)
[:print:] Printable characters (same as [:graph:] plus the space character)
PCRE
Perl 兼容正则表达式允许使用语法的十六进制字符代码
\x00
例如,日语字符 あ 的 PCRE 正则表达式将是
\xE3\x81\x82
除了上面列出的 POSIX 字符类之外,PCRE 还提供了[:ascii:] 字符类,它是[\x00-\x7F] 的便捷简写。
GNU 版本的grep 支持使用-P 标志的PCRE,但BSD grep(例如在Mac OS X 上)不支持。 GNU 和 BSD find 都不支持 PCRE 正则表达式。
查找文件
GNU find 支持 POSIX 正则表达式(感谢 iscfrc 指出纯 find 解决方案以避免产生额外的进程)。以下命令将列出当前目录下包含不可打印控制字符的所有文件名(但不包括目录名):
find -type f -regextype posix-basic -regex '^.*/[^/]*[[:cntrl:]][^/]*$'
正则表达式有点复杂,因为-regex 选项必须匹配整个文件路径,而不仅仅是文件名,并且因为我假设我们不想仅仅因为它们而删除具有正常名称的文件位于名称包含控制字符的目录中。
要删除匹配的文件,只需将-delete 选项传递给find,在所有其他选项之后(这很关键;将-delete 作为第一个选项传递将消除您当前的目录):
find -type f -regextype posix-basic -regex '^.*/[^/]*[[:cntrl:]][^/]*$' -delete
我强烈建议先运行命令不带-delete,这样你就可以看到什么会在为时已晚之前被删除。
如果您还传递了-print 选项,您可以在命令运行时看到正在删除的内容:
find -type f -regextype posix-basic -regex '^.*/[^/]*[[:cntrl:]][^/]*$' -print -delete
要清除任何包含控制字符的路径(文件或目录),可以简化正则表达式,您可以删除-type 选项:
find -regextype posix-basic -regex '.*[[:cntrl:]].*' -print -delete
使用此命令,如果目录名包含控制字符,即使目录中的文件名都没有,它们也会全部被删除。
更新:查找非 ASCII 和控制字符
看起来您的文件包含非 ASCII 字符和 ASCII 控制字符。事实证明,[:ascii:] 不是 POSIX 字符类,但它是 PCRE 提供的。我找不到一个 POSIX 正则表达式来做这件事,所以 Perl 来拯救。我们仍将使用find 来遍历我们的目录树,但我们会将结果传递给 Perl 进行处理。
为了确保我们可以处理包含换行符的文件名(在这种情况下似乎很可能),我们需要使用find 的-print0 参数(在GNU 和BSD 版本上都支持);这将使用空字符 (0x00) 而不是换行符分隔记录,因为空字符是 Linux 上唯一不能出现在有效文件名中的字符。我们需要将相应的标志 -0 传递给我们的 Perl 代码,以便它知道如何分隔记录。以下命令将递归打印当前目录中的每个路径:
find . -print0 | perl -n0e 'print $_, "\n"'
请注意,此命令仅生成 Perl 解释器的单个实例,这对性能有好处。起始路径参数(在这种情况下,CWD 为 .)在 GNU find 中是可选的,但在 Mac OS X 上的 BSD find 中是必需的,因此为了可移植性,我将其包括在内。
现在是我们的正则表达式。这是一个 PCRE 正则表达式匹配包含非 ASCII 或不可打印(即控制)字符(或两者)的名称:
[[:^ascii:][:cntrl:]]
以下命令将打印当前目录中匹配此正则表达式的所有路径(目录或文件):
find . -print0 | perl -n0e 'chomp; print $_, "\n" if /[[:^ascii:][:cntrl:]]/'
chomp 是必要的,因为它会从每个路径中去除尾随的空字符,否则会匹配我们的正则表达式。要删除匹配的文件和目录,我们可以使用以下命令:
find . -print0 | perl -MFile::Path=remove_tree -n0e 'chomp; remove_tree($_, {verbose=>1}) if /[[:^ascii:][:cntrl:]]/'
这也会在命令运行时打印出正在删除的内容(尽管会解释控制字符,因此输出与ls 的输出不完全匹配)。