如何运行此“ find”命令,但仅在非二进制文件上运行?


8

我想从递归目录层次结构中的所有文件中删除尾随空格。我用这个:

find * -type f -exec sed 's/[ \t]*$//' -i {} \;

这可行,但也会从找到的二进制文件中删除尾随的“空白”,这是不希望的。

我如何find避免在二进制文件上运行此命令?


Unix文件系统在“二进制”和“非二进制”文件之间没有区别。如果不查看文件内部,就无法分辨文件中的数据类型。
Wooble

@Wooble:是的,但是有些命令file可以检查数据。
John Feminella 2011年

Answers:


4

您可以尝试使用Unix file命令来帮助识别不需要的文件,但是我认为,如果您明确指定要击中的文件而不是不需要的文件,则可能会更好。

find * -type f \( -name \*.java -o -name \*.c -o -name \*.sql \) -exec sed 's/[ \t]*$//' -i {} \;

为了避免遍历源代码控制文件,您可能需要类似

find * \! \( -name .svn -prune \) -type f \( -name \*.java -o -name \*.c -o -name \*.sql \) -exec sed 's/[ \t]*$//' -i {} \;

根据您的外壳,您可能需要也可能不需要一些反斜杠。


2
我不了解您,但是我们所有的Java源文件始终都在标准UTF-8中,因此sed命令不会总是对所有这些文件执行正确的操作。我也有无法-i选择sed的系统。很难编写一个可移植的shell命令,不是吗?
tchrist 2011年

4

可以在命令行上完成。

$ find . -type f -print|xargs file|grep ASCII|cut -d: -f1|xargs sed 's/[ \t]*$//' -i

3

最简单,最可移植的答案是运行以​​下命令:

#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
my @dirs = (@ARGV == 0) ? <*> : @ARGV;
find sub {
    next unless -f && -T;
    system('perl', '-i', '-pe', 's/[\t\xA0 ]+$//', $File::Find::name);
} => @dirs;

我将在下面解释原因,在这里我还将展示如何仅使用命令行来执行此操作,以及如何处理像ISO-8859-1(Latin-1)和UTF-8这样的跨ASCII文本文件,这些文件通常都没有-其中包含ASCII空格。


故事的其余部分

问题是find(1)不支持-Tfiletest运算符,如果支持,则也不识别编码-您绝对需要检测UTF-8(事实上是标准Unicode编码)。

您可以做的是通过抛出二进制文件的层运行文件名列表。例如

$ find . -type f | perl -nle 'print if -T' | xargs sed -i 's/[ \t]*$//'

但是,现在您在文件名中使用空格时遇到了麻烦,因此您需要使用空终止来延迟此操作:

$ find . -type f -print0 | perl -0 -nle 'print if -T' | xargs -0 sed -i 's/[ \t]*$//'

您可以做的另一件事是不使用findbut find2perl,因为Perl -T已经了解:

$ find2perl * -type T -exec sed 's/[ \t]*$//' -i {} \; | perl

而且,如果您希望Perl假定其文件位于UTF-8中,请使用

$ find2perl * -type T -exec sed 's/[ \t]*$//' -i {} \; | perl -CSD

或者,您可以将生成的脚本保存在文件中并进行编辑。您确实不应该只-T在任何旧文件上运行filetest,而应仅在最初由决定的纯文件上运行-f。否则,您将面临打开设备特价,阻塞fifos等风险。

但是,如果要执行所有操作,则最好完全跳过sed(1)。一方面,它更具可移植性,因为POSIX版本的sed(1)无法理解-i,而所有版本的Perl都可以理解。sed的最新版本非常适合-iti首次出现的Perl中非常有用的选项。

这也给您修复正则表达式的机会。您确实应该使用与一个或多个尾随水平空白匹配的模式,而不仅仅是它们的零,否则,由于不必要的复制,运行速度会变慢。也就是说,这是:

 s/[ \t]*$//

应该

 s/[ \t]+$//

然而,如何让sed的(1)了解,需要一个非POSIX扩展,通常要么-R对系统ⅤUnix系统像Solaris或Linux或-EBSD的那些象OpenBSD系统或者MacOS。我怀疑在AIX下是不可能的。您知道,编写可移植的shell比移植的shell脚本更容易。

0xA0上的警告

尽管这些是ASCII中唯一的水平空白字符,但ISO-8859-1和Unicode均在代码点U + 00A0处具有NO-BREAK SPACE。这是在许多Unicode语料库中发现的前两个非ASCII字符之一,最近我看到很多人的正则表达式代码中断是因为他们忘记了它。

那么,为什么不这样做呢?

$ find * -print0 | perl -0 -nle 'print if -f && -T' | xargs -0 perl -i -pe 's/[\t\xA0 ]+$//'

如果你可能有UTF-8的文件来处理,加-CSD,如果你正在运行的Perl V5.10或更高版本,可以使用\h的水平空白和\R一个通用的断行,其中包括\r\n\r\n\f\cK\x{2028},和\x{2029}

$ find * -print0 | perl -0 -nle 'print if -f && -T' | xargs -0 perl -CSD -i -pe 's/\h+(?=\R*$)//'

不管它们的换行符如何,这都适用于所有UTF-8文件,摆脱了尾随的水平空白(Unicode字符属性HorizSpace),包括在每行末尾出现在Unicode换行符(包括CRLF组合)之前的讨厌的NO-BREAK SPACE。

它也比sed(1)版本具有更多的可移植性,因为只有一个perl(1)实现,但有许多sed(1)。

我看到的主要问题仍然是find(1),因为在某些真正顽固的系统(您知道自己是谁,AIX和Solaris)上,它不了解超临界-print0指令。如果是这种情况,那么您应该直接使用File::FindPerl中的模块,而不使用其他Unix实用程序。这是您的代码的纯Perl版本,它不依赖任何其他内容:

#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
my @dirs = (@ARGV == 0) ? <*> : @ARGV;
find sub {
     next unless -f && -T;
     system('perl', '-i', '-pe', 's/[\t\xA0 ]+$//', $File::Find::name);  
} => @dirs;

如果仅在ASCII或ISO-8859-1文本文件上运行,那很好,但是在ASCII或UTF-8文件上运行时-CSD,请在内部调用Perl的开关中添加。

如果您对ASCII,ISO-8859-1和UTF-8的全部三种使用混合编码,那么我担心您还有另一个问题。:(您将不得不根据每个文件找出编码,并且从来没有一种很好的方法来猜测。

Unicode空格

作为记录,Unicode具有26个不同的空白字符。您可以使用单字符工具来嗅这些了。几乎只见过前三个水平空白字符:

$ unichars '\h'
 ---- U+0009 CHARACTER TABULATION
 ---- U+0020 SPACE
 ---- U+00A0 NO-BREAK SPACE
 ---- U+1680 OGHAM SPACE MARK
 ---- U+180E MONGOLIAN VOWEL SEPARATOR
 ---- U+2000 EN QUAD
 ---- U+2001 EM QUAD
 ---- U+2002 EN SPACE
 ---- U+2003 EM SPACE
 ---- U+2004 THREE-PER-EM SPACE
 ---- U+2005 FOUR-PER-EM SPACE
 ---- U+2006 SIX-PER-EM SPACE
 ---- U+2007 FIGURE SPACE
 ---- U+2008 PUNCTUATION SPACE
 ---- U+2009 THIN SPACE
 ---- U+200A HAIR SPACE
 ---- U+202F NARROW NO-BREAK SPACE
 ---- U+205F MEDIUM MATHEMATICAL SPACE
 ---- U+3000 IDEOGRAPHIC SPACE

$ unichars '\v'
 ---- U+000A LINE FEED (LF)
 ---- U+000B LINE TABULATION
 ---- U+000C FORM FEED (FF)
 ---- U+000D CARRIAGE RETURN (CR)
 ---- U+0085 NEXT LINE (NEL)
 ---- U+2028 LINE SEPARATOR
 ---- U+2029 PARAGRAPH SEPARATOR

0

GNU grep非常擅长识别文件是否为二进制文件。除了Solaris,我确定默认情况下还没有安装其他未安装GNU grep的平台,但是像Solaris一样,我确定可以安装它。

perl -pi -e 's{[ \t]+$}{}g' `grep -lRIP '[ \t]+$' .`

如果您使用的是Solaris,请替换grep/opt/csw/bin/ggrep

这些grep标志执行以下操作:l仅列出匹配文件的文件名,R是递归的,I仅匹配文本文件(忽略二进制文件),并且P用于与perl兼容的正则表达式语法。

perl部分就地修改文件,删除所有尾随空格/制表符。

最后:如果UTF8是个问题,只要grep您的构建是使用UTF8支持构建的,tchrist的回答与我的回答就足够了(不过,软件包维护者通常会尝试提供这种功能)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.