我有一些旧的迁移文件包含不可打印的字符。我想找到所有具有此类名称的文件,并将其从系统中完全删除。
示例:
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
时所看到的示例屏幕截图:
我想找到带有不可打印字符的这些文件,然后删除它们。
答案 0 :(得分:25)
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代码中,0x00
到0x1F
和0x7F
代表控制字符,例如ESC
(0x1B
)。这些控制字符最初并不打算打印,即使它们中的一些(如换行符0x0A
)可以被解释和显示。
在我的系统上,ls
默认情况下会将所有控制字符显示为?
,除非我通过--show-control-chars
选项。我猜你要删除的文件包含ASCII控制字符,而不是非ASCII字符。这是一个重要的区别:如果删除包含非ASCII字符的文件名,您可能会吹掉恰好用另一种语言命名的合法文件。
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)
Perl兼容的正则表达式允许使用语法
的十六进制字符代码\x00
例如,日语字符あ
的PCRE正则表达式为
\xE3\x81\x82
除了上面列出的POSIX字符类之外,PCRE还提供了[:ascii:]
字符类,这是[\x00-\x7F]
的便捷简写。
GNU的grep
版本使用-P
标志支持PCRE,但BSD grep
(例如Mac OS X上)不支持PCRE。 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
,这样您就可以看到将要删除的内容为时已晚。< / p>
如果您还传递-print
选项,则可以在命令运行时查看正在删除的内容:
find -type f -regextype posix-basic -regex '^.*/[^/]*[[:cntrl:]][^/]*$' -print -delete
要删除包含控制字符的任何路径(文件或目录),可以简化正则表达式并删除-type
选项:< / p>
find -regextype posix-basic -regex '.*[[:cntrl:]].*' -print -delete
使用此命令,如果目录名称包含控制字符,即使目录中没有任何文件名,也会删除 all 。
您的文件似乎包含非ASCII字符和 ASCII控制字符。事实证明,[:ascii:]
不是一个POSIX字符类,但是由PCRE提供。我无法找到POSIX正则表达式,所以它是Perl救援。我们仍然使用find
来遍历我们的目录树,但我们会将结果传递给Perl进行处理。
为了确保我们可以处理包含换行符的文件名(在这种情况下似乎很可能),我们需要使用-print0
find
参数(GNU和BSD版本都支持);这将使用空字符(0x00
)而不是换行符分隔记录,因为空字符是唯一不能在Linux上的有效文件名中的字符。我们需要将相应的标志-0
传递给我们的Perl代码,以便它知道记录是如何分开的。以下命令将以递归方式打印当前目录中的每个路径:
find . -print0 | perl -n0e 'print $_, "\n"'
请注意,此命令仅生成Perl解释器的单个实例,这有助于提高性能。起始路径参数(在本例中为.
的{{1}})在GNU CWD
中是可选的,但在Mac OS X上的BSD find
中是必需的,所以我&#39;包括它是为了便于携带。
现在为我们的正则表达式。这是一个PCRE正则表达式匹配名称,包含非ASCII或不可打印(即控制)字符(或两者):
find
以下命令将在当前目录中打印与此正则表达式匹配的所有路径(目录或文件):
[[:^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:]]/'
的输出不完全匹配)。
答案 1 :(得分:1)
到目前为止,您可能已经解决了我的问题,但它对我的案例并不适用,因为当我使用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编号前缀{{1} })。结果将通过管道传输到-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
将遍历所有条目,提取inode编号并将inode传递给while
,然后将删除该文件。
答案 2 :(得分:0)
您只能使用grep:
打印包含反斜杠的行ls -lb | grep \\\\
答案 3 :(得分:0)
可以将PCRE与grep -P一起使用,而不是使用find(不幸的是)。您可以使用exec链接查找grep。使用PCRE(perl regex),我们可以使用ascii类并找到任何非ascii的char。
find . -type f -exec sh -c "echo \"{}\" | grep -qP '[^[:ascii:]]'" \; -exec rm {} \;
除非第一个exec返回非错误代码,否则不会执行以下exec。在这种情况下,它表示表达式与文件名匹配。我使用sh -c因为-exec不喜欢管道。
答案 4 :(得分:0)
根据此answer,尝试:
LC_ALL=C find . -regex '.*[^ -~].*' -print # -delete
或:
LC_ALL=C find . -type f -regex '*[^[:alnum:][:punct:]]*' -print # -delete
注意:正确打印文件后,请删除#
字符。