我想从递归目录层次结构中的所有文件中删除尾随空格。我使用这个:
find * -type f -exec sed 's/[ \t]*$//' -i {} \;
这是可行的,但也会从找到的二进制文件中删除尾随的“空格”,这是不可取的。
我如何find
避免在二进制文件上运行此命令?
答案1
您可以尝试使用 Unixfile
命令来帮助识别您不想要的文件,但我认为如果您明确指定要访问的文件而不是不想要的文件可能会更好。
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 {} \;
根据您的 shell,您可能需要或不需要某些反斜杠。
答案2
可以在命令行上完成。
$ 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;
我在下面解释了原因,同时还展示了如何仅使用命令行来执行此操作,以及如何处理跨 ASCII 文本文件(如 ISO-8859-1(Latin-1)和 UTF-8,其中通常包含非 ASCII 空格)。
故事的其余部分
问题是寻找(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]*$//'
您可以做的另一件事是使用 not 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,而应该只对那些由 确定的纯文本文件运行 filetest -f
。否则,您可能会面临打开设备特殊功能、阻塞 fifo 等风险。
然而,如果你要这么做,你最好跳过sed(1)完全一样。首先,它更易于移植,因为 POSIX 版本的sed(1)不理解-i
,而所有版本的 Perl 都理解 。sed-i
在 ti 首次出现的地方,精心地挪用了 Perl 中非常有用的选项。
这也让你有机会修复你的正则表达式。你确实应该使用一个匹配一个或多个尾随水平空格的模式,而不是零个,否则你会因为不必要的复制而运行得更慢。也就是说:
s/[ \t]*$//
应该
s/[ \t]+$//
然而,如何获得sed(1) 要理解这一点,需要非 POSIX 扩展,通常要么-R
用于 System Ⅴ Unices,如 Solaris 或 Linux,要么-E
用于 BSD,如 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)。
我认为目前仍然存在的主要问题是寻找(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 空格
需要说明的是,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
答案4
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 是一个问题,那么 tchrist 的回答加上我的回答就足够了,前提是grep
你的构建是有 UTF8 支持构建的(不过,通常软件包维护者会尝试提供这种功能)。