查找并删除文件名包含非ASCII字符的文件

20

我有一些旧的迁移文件,其中包含不可打印的字符。我想找到所有这样命名的文件并从系统中彻底删除它们。

例子:

ls -l
-rwxrwxr-x 1 cws cws      0 Dec 28  2011 ??"??

ls -lb
-rwxrwxr-x 1 cws cws      0 Dec 28  2011 \a\211"\206\351

我想找到所有这样的文件。

这是一个示例截图,显示了当我在这些文件夹中执行ls命令时看到的内容:

enter image description here

我想找到带有不可打印字符的文件,并将其删除。

5个回答

31

非ASCII字符

ASCII字符代码的范围从十六进制的0x000x7F。因此,任何代码大于0x7F的字符都是非ASCII字符。这包括UTF-8中的大部分字符(ASCII代码本质上是UTF-8的子集)。例如,日语字符

在UTF-8中,E3 81 82以十六进制编码。

E3 81 82

UTF-8自Red Hat Linux 8.0版(2002年)、SuSE Linux 9.1版(2004年)和Ubuntu Linux 5.04版(2005年)以来一直是默认的字符编码。

ASCII控制字符

在ASCII代码中,0x000x1F0x7F表示控制字符,例如ESC0x1B)。这些控制字符最初并不是可打印的,尽管其中一些,如换行符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兼容正则表达式(Perl Compatible Regular Expressions)允许使用语法表示十六进制字符编码。

\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:]][^/]*$'

"The regex is a little complicated because the -regex option has to match the entire file path, not just the filename, and because I'm assuming that we don't want to blow away files with normal names simply because they are inside directories with names containing control characters.
To delete the matching files, simply pass the -delete option to find, after all other options (this is critical; passing -delete as the first option will blow away everything in your current directory):"
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进行处理。

为了确保我们可以处理包含换行符的文件名(在这种情况下似乎很可能),我们需要使用-print0参数来调用find(在GNU和BSD版本上都受支持);这将使用空字符(0x00)而不是换行符分隔记录,因为空字符是Linux上不能出现在有效文件名中的唯一字符。我们需要向我们的Perl代码传递相应的标志-0,以便它知道如何分隔记录。以下命令将递归打印当前目录中的每个路径:

find . -print0 | perl -n0e 'print $_, "\n"'

请注意,该命令仅生成单个Perl解释器实例,这有利于性能。在GNU find中,起始路径参数(在本例中为)是可选的,但在Mac OS X上的BSD find中是必需的,因此我已将其包含以确保可移植性。
现在来看我们的正则表达式。这是一个PCRE正则表达式,匹配包含非ASCII或非可打印(即控制)字符(或两者都有)的名称:
[[:^ascii:][:cntrl:]]

以下命令将打印当前目录中与该正则表达式匹配的所有路径(目录或文件):
```bash ls -R | grep 'regex' ```
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 的输出)。


2
此外,您还可以使用[:print:][:graph:],请参见http://www.faqs.org/docs/abs/HTML/regexp.html。 - EverythingRightPlace
不用谢。我本来要自己构建一个答案,但你比我快 :)我喜欢使用execdir。 - EverythingRightPlace
感谢您提供的信息丰富的答案。当我尝试使用 [^[:ascii:]] 时,会出现“查找:无效的字符类名称”的错误。您有什么想法是什么原因导致了这个问题? - Rohit Chopra
我尝试了这个答案中的建议,但不幸的是,它们无法跟踪文件名中包含非打印字符的文件。如果有人能够调整此问题,我刚刚更新了我的问题,并包含了一张截图,希望能有所帮助 :) - Rohit Chopra
@RohitChopra 显然, [:ascii:] 并非完全符合 POSIX。请查看我的更新以获取另一种解决方案。 - ThisSuitIsBlackNot
在没有使用 -type f 的情况下,添加 -mindepth 1 可能是有意义的,以免删除目录本身 :) - x-yuri

6

根据这个答案,尝试:

LC_ALL=C find . -regex '.*[^ -~].*' -print # -delete

或者:

LC_ALL=C find . -type f -regex '*[^[:alnum:][:punct:]]*' -print # -delete

注意:文件正确打印后,请删除#字符。

另请参阅:如何grep所有非ASCII字符


3
到现在为止,你可能已经解决了你的问题,但对于我的情况并不起作用,因为我有一些文件没有被"find"命令使用"-regex"选项找到。所以我使用"ls"命令开发了这个解决办法。希望它能对其他人有用。
基本上,对我有效的是这样做的:
ls -1 -R -i | grep -a "[^A-Za-z0-9_.':@ /-]" | while read f; do inode=$(echo "$f" | cut -d ' ' -f 1); find -inum "$inode" -delete; done

分解为若干部分:

ls -1 -R -i

这将以递归方式 (-R) 列出当前目录下的文件 (ls),每行一个文件 (-1),并在每个文件前加上其inode号码 (-i)。结果将通过管道传输到 grep

grep -a "[^A-Za-z0-9_.':@ /-]"

考虑每个输入都是文本(-a),即使最终是二进制的也要这么做。grep会让包含与列表中指定字符不同的字符的行通过。结果将被管道传输到while

while read f
do
    inode=$(echo "$f" | cut -d ' ' -f 1)
    find -inum "$inode" -delete
done

这个while循环将遍历所有条目,提取i节点号并将i节点传递给find,然后删除文件。


1

可以使用PCRE与grep -P一起使用,但不可与find一起使用(遗憾的是)。您可以使用exec将find与grep链接起来。使用PCRE(perl regex),我们可以使用ascii类并查找任何非ascii字符。

find . -type f -exec sh -c "echo \"{}\" | grep -qP '[^[:ascii:]]'" \; -exec rm {} \;

以下的exec命令只有在第一个命令返回非错误代码时才会被执行。在这种情况下,这意味着表达式与文件名匹配。我使用了sh -c,因为-exec不支持管道。

0

你可以使用grep打印只包含反斜杠的行:

ls -lb | grep \\\\

这需要使用递归。有数百个文件夹中包含有这样的文件。 - Rohit Chopra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接