我想从递归目录层次结构中的所有文件中删除尾随空格。我用这个:
find * -type f -exec sed 's/[ \t]*$//' -i {} \;
这可行,但也会从找到的二进制文件中删除尾随的“空白”,这是不希望的。
我如何find
避免在二进制文件上运行此命令?
file
可以检查数据。
我想从递归目录层次结构中的所有文件中删除尾随空格。我用这个:
find * -type f -exec sed 's/[ \t]*$//' -i {} \;
这可行,但也会从找到的二进制文件中删除尾随的“空白”,这是不希望的。
我如何find
避免在二进制文件上运行此命令?
file
可以检查数据。
Answers:
您可以尝试使用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 {} \;
根据您的外壳,您可能需要也可能不需要一些反斜杠。
-i
选择sed的系统。很难编写一个可移植的shell命令,不是吗?
最简单,最可移植的答案是运行以下命令:
#!/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)不支持-T
filetest运算符,如果支持,则也不识别编码-您绝对需要检测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]*$//'
您可以做的另一件事是不使用find
but 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的最新版本非常适合-i
ti首次出现的Perl中非常有用的选项。
这也给您修复正则表达式的机会。您确实应该使用与一个或多个尾随水平空白匹配的模式,而不仅仅是它们的零,否则,由于不必要的复制,运行速度会变慢。也就是说,这是:
s/[ \t]*$//
应该
s/[ \t]+$//
然而,如何让sed的(1)了解,需要一个非POSIX扩展,通常要么-R
对系统ⅤUnix系统像Solaris或Linux或-E
BSD的那些象OpenBSD系统或者MacOS。我怀疑在AIX下是不可能的。您知道,编写可移植的shell比移植的shell脚本更容易。
尽管这些是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::Find
Perl中的模块,而不使用其他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具有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
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的回答与我的回答就足够了(不过,软件包维护者通常会尝试提供这种功能)。