【问题标题】:find and delete files with non-ascii names查找和删除具有非 ASCII 名称的文件
【发布时间】:2013-10-09 09:17:12
【问题描述】:

我有一些包含不可打印字符的旧迁移文件。我想找到所有具有此类名称的文件并将它们从系统中完全删除。

例子:

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 时看到的示例屏幕截图:

我想找到这些带有不可打印字符的文件并删除它们。

【问题讨论】:

    标签: linux bash shell


    【解决方案1】:

    您可以使用 grep 仅打印包含反斜杠的行:

    ls -lb | grep \\\\
    

    【讨论】:

    • 这需要递归。包含此类文件的文件夹中有数百个文件夹
    【解决方案2】:

    非 ASCII 字符

    ASCII 字符代码范围从 0x000x7F 十六进制。因此,任何代码大于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 码中,0x000x1F0x7F 表示控制字符,例如 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 的输出不完全匹配)。

    【讨论】:

    • 另外你也可以使用[:print:][:graph:],见faqs.org/docs/abs/HTML/regexp.html
    • 不客气。我打算自己构建一个答案,但你更快:) 我喜欢使用 execdir。
    • 感谢您提供的信息丰富的回答。当我尝试 [^[:ascii:]] 时,我得到 find: Invalid character class name。知道是什么原因造成的吗?
    • 我尝试了这个答案中的建议,但不幸的是,他们无法跟踪名称中包含不可打印字符的文件。我刚刚更新了我的问题以包含一个屏幕截图,如果这将有助于任何人调整这个:)
    • @RohitChopra 显然,[:ascii:] 毕竟不是 POSIX。请参阅我的更新以获取其他解决方案。
    【解决方案3】:

    到目前为止,您可能已经解决了您的问题,但它对我的情况并不适用,因为当我使用 -regex 开关时,find 没有显示文件。所以我使用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 将遍历所有条目,提取inode 编号并将inode 传递给find,然后find 将删除该文件。

    【讨论】:

      【解决方案4】:

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

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

      除非第一个返回非错误代码,否则以下 exec 将不会执行。在这种情况下,这意味着表达式与文件名匹配。我使用 sh -c 因为 -exec 不喜欢管道。

      【讨论】:

        【解决方案5】:

        基于这个answer,试试:

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

        或:

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

        注意:文件打印正确后,去掉#字符。

        另请参阅:How do I grep for all non-ASCII characters

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-15
          • 2023-01-11
          • 2017-01-10
          • 1970-01-01
          相关资源
          最近更新 更多