8

Я хочу удалить конечные пробелы из всех файлов в рекурсивной иерархии каталогов. Я использую это:

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

Это работает, но также удаляет конечные пробелы из найденных двоичных файлов, что нежелательно.

Как мне сказать find чтобы избежать запуска этой команды на двоичных файлах?

4 ответа4

4

Вы можете попытаться использовать file команду Unix, чтобы помочь идентифицировать файлы, которые вам не нужны, но я думаю, что было бы лучше, если бы вы явно указали, какие файлы вы хотите использовать, а не те, которые вы не хотите.

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 {} \;

Вы можете или не можете нуждаться в некоторых обратных слешах в зависимости от вашей оболочки.

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;

Ниже я объясню, почему, где я также показываю, как это сделать, используя только командную строку, а также как обращаться с текстовыми файлами trans-ASCII, такими как ISO-8859-1 (Latin-1) и UTF-8, которые после -Ассии пробелов в них.


Остальная часть истории

Проблема в том, что 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]*$//'

Другая вещь, которую вы можете сделать, это использовать not find но 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 . В противном случае вы рискуете открыть специальные устройства, заблокировать на пятерках и т.д.

Однако, если вы собираетесь делать все это, вы можете вообще пропустить sed(1). Во-первых, он более переносим, поскольку версия sed(1) для POSIX не понимает -i , в то время как все версии Perl понимают. Версии sed последних дней с любовью присваивают очень полезную опцию -i из Perl, где впервые появляется ti.

Это также дает вам возможность исправить ваши регулярные выражения. Вы действительно должны использовать шаблон, который соответствует одному или нескольким конечным горизонтальным пробелам, а не просто их нулю, иначе вы будете медленнее работать из-за ненужного копирования. То есть это:

 s/[ \t]*$//

должно быть

 s/[ \t]+$//

Однако, как получить sed(1), чтобы понять, что для этого требуется расширение, отличное от POSIX, обычно это либо -R для системных System Unices, таких как Solaris или Linux, либо -E для BSD, таких как OpenBSD или MacOS. Я подозреваю, что это невозможно под AIX. Знаете, проще написать переносную оболочку, чем переносимый сценарий оболочки.

Предупреждение о 0xA0

Хотя это единственные горизонтальные пробельные символы в ASCII, оба стандарта ISO-8859-1 и, следовательно, также Unicode имеют пробел NO-BREAK в кодовой точке U+00A0. Это один из двух лучших не-ASCII символов, встречающихся во многих Unicode-корпусах, и в последнее время я видел, как многие люди ломали код регулярного выражения, потому что они забыли об этом.

Так почему бы тебе просто не сделать это:

$ 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), включая надоедливый пробел NO-BREAK, возникающий до разрыва строки Unicode (включая комбинации CRLF) в конце каждой строки ,

Он также намного более переносим, чем версия sed(1), потому что существует только одна реализация perl(1), но много sed(1).

Основная проблема, которую я вижу, остается с find(1), поскольку в некоторых действительно непокорных системах (вы знаете, кто вы, AIX и Solaris) она не понимает директиву supercritical -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, чтобы прослушать их. Только первые три горизонтальных пробела встречаются почти всегда:

$ 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 является проблемой, ответ tchrist в сочетании с моим должен быть достаточным, при условии, что ваша сборка grep была собрана с поддержкой UTF8 (хотя, как правило, сопровождающие пакетов пытаются предоставить такую функциональность).

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .