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

8

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

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

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

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

Джон Феминелла
источник
Файловые системы Unix не делают различий между «двоичными» и «недвоичными» файлами; невозможно определить тип данных в файле, не заглядывая в него.
Wooble
@Wooble: Это правильно, но есть такие команды, fileкоторые могут проверять данные.
Джон Феминелла

Ответы:

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

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

Берт F
источник
2
Я не знаю о вас, но все наши исходные файлы Java всегда находятся в стандартном UTF-8, так что команда sed не всегда будет работать правильно со всеми из них. У меня тоже есть системы без -iопции sed . Трудно написать команду переносимой оболочки, не так ли?
tchrist
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]*$//'

Еще одна вещь, которую вы можете сделать, это не использовать, 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

Или вы можете сохранить полученный скрипт в файле и отредактировать его. Вы действительно должны не просто запустить -Tfiletest для любого старого файла, а только для тех, которые являются простыми файлами, как определено вначале -f. В противном случае вы рискуете открыть специальные устройства, заблокировать на пятерках и т. Д.

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

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

 s/[ \t]*$//

должно быть

 s/[ \t]+$//

Однако, как получить sed (1), чтобы понять, что для этого требуется расширение, -Rотличное от POSIX, как правило, для System 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для общего LineBreak, который включает в себя \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) она не понимает -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
tchrist
источник
0

GNU grep довольно хорошо определяет, является ли файл двоичным или нет. Помимо Solaris, я уверен, что есть другие платформы, которые не поставляются с GNU grep, установленным по умолчанию, но, как и Solaris, я уверен, что вы можете установить его.

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

Если вы в Солярисе, вы бы заменили grepна /opt/csw/bin/ggrep.

Эти grepфлаги выполняют следующие действия : lтолько списки имен файлов для сопоставления файлов, Rявляется рекурсивным, Iсоответствует только текстовые файлы (игнорирует двоичные файлы), а также Pдля Perl-совместимый синтаксис регулярных выражений.

Часть perl изменяет файл на месте, удаляя все конечные пробелы / табуляции.

И наконец: если UTF8 является проблемой, ответ tchrist в сочетании с моим должен быть достаточным, при условии, что grepваша сборка была построена с поддержкой UTF8 (хотя, как правило, сопровождающие пакетов пытаются предоставить такую ​​функциональность).

Брайан Ванденберг
источник