Jak uruchomić to polecenie `find`, ale tylko na plikach innych niż binarne?

8

Chcę usunąć końcowe białe znaki ze wszystkich plików w rekurencyjnej hierarchii katalogów. Używam tego:

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

Działa to, ale usuwa również końcowe białe znaki z znalezionych plików binarnych, co jest niepożądane.

Jak mam powiedzieć, findżeby unikać uruchamiania tego polecenia na plikach binarnych?

John Feminella
źródło
Uniksowe systemy plików nie rozróżniają plików „binarnych” i „niebinarnych”; nie ma sposobu, aby powiedzieć, jaki typ danych znajduje się w pliku bez zaglądania do niego.
Wooble,
@Wooble: To prawda, ale istnieją takie polecenia, filektóre mogą sprawdzać dane.
John Feminella,

Odpowiedzi:

4

Możesz spróbować użyć filepolecenia Unix, aby zidentyfikować pliki, których nie chcesz, ale myślę, że lepiej byłoby, jeśli wyraźnie określisz, które pliki chcesz trafić, a nie te, których nie chcesz.

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

aby uniknąć przechodzenia do plików kontroli źródła, możesz chcieć czegoś takiego

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

W zależności od powłoki możesz, ale nie potrzebujesz, odwrotnych ukośników.

Bert F.
źródło
2
Nie wiem o tobie, ale wszystkie nasze pliki źródłowe Java są zawsze w standardowym UTF-8, więc komenda sed nie zawsze zrobi to dobrze. Mam również systemy bez -iopcji sed . Trudno jest napisać przenośne polecenie powłoki, prawda?
tchrist
4

Można to zrobić w wierszu polecenia.

$ find . -type f -print|xargs file|grep ASCII|cut -d: -f1|xargs sed 's/[ \t]*$//' -i
Vijay
źródło
3

Najprostszą i najbardziej przenośną odpowiedzią jest uruchomienie tego:

#!/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;

Wyjaśnię, dlaczego poniżej, gdzie również pokazuję, jak to zrobić za pomocą wiersza polecenia, a także jak radzić sobie z plikami tekstowymi trans-ASCII, takimi jak ISO-8859-1 (Latin-1) i UTF-8, które również nie mają -ASCII w nich białe znaki.


Reszta historii

Problem polega na tym, że find (1) nie obsługuje -Toperatora testowania plików, ani nie rozpoznaje kodowania, jeśli tak - co absolutnie musisz wykryć UTF-8, de facto standardowe kodowanie Unicode.

Co możesz zrobić, to uruchomić listę nazw plików przez warstwę, która wyrzuca pliki binarne. Na przykład

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

Jednak teraz masz problem z białymi spacjami w nazwach plików, więc musisz opóźnić to z zerowym zakończeniem:

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

Inną rzeczą, którą możesz zrobić, to nie używać, findale find2perlponieważ Perl -Tjuż rozumie :

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

A jeśli chcesz, aby Perl założył, że jego pliki znajdują się w UTF-8, użyj

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

Lub możesz zapisać wynikowy skrypt w pliku i edytować go. Naprawdę naprawdę nie powinieneś uruchamiać -Ttestowania plików na żadnym starym pliku, ale tylko na tych, które są zwykłymi plikami, jak określa to po raz pierwszy -f. W przeciwnym razie ryzykujesz otwarcie ofert specjalnych urządzeń, blokowanie na FIFO itp.

Jeśli jednak masz zamiar to zrobić, równie dobrze możesz całkowicie pominąć sed (1). Po pierwsze, jest bardziej przenośny, ponieważ wersja sed (1) POSIX nie rozumie -i, podczas gdy wszystkie wersje Perla tak. Ostatnie wersje sed z miłością zawłaszczyły bardzo przydatną -iopcję od Perla, w którym po raz pierwszy pojawia się ti.

Daje to również możliwość naprawy wyrażenia regularnego. Naprawdę powinieneś używać wzorca, który pasuje do jednej lub więcej końcowych białych spacji, a nie tylko ich zero, w przeciwnym razie będziesz działać wolniej z niepotrzebnego kopiowania. To jest to:

 s/[ \t]*$//

Powinien być

 s/[ \t]+$//

Jednak, jak uzyskać sed (1), aby zrozumieć, że wymaga rozszerzenia nie-POSIX, zwykle albo -Rdla Uniksów Systemu like, jak Solaris lub Linux, lub -Edla BSD, takich jak OpenBSD lub MacOS. Podejrzewam, że jest to niemożliwe w systemie AIX. Wiesz, łatwiej jest napisać przenośną powłokę niż przenośny skrypt powłoki.

Ostrzeżenie o 0xA0

Chociaż są to jedyne poziome znaki białych znaków w ASCII, zarówno ISO-8859-1, a tym samym także Unicode, mają PRZESTRZEŃ BEZ PRZERWU w punkcie kodowym U + 00A0. Jest to jeden z dwóch najlepszych znaków spoza ASCII znalezionych w wielu korpusach Unicode, a ostatnio widziałem, jak wielu ludzi łamie kod wyrażenia regularnego, ponieważ o tym zapomnieli.

Dlaczego więc tego nie zrobisz:

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

Jeśli możesz mieć UTF-8 pliki do czynienia z, dodać -CSD, i jeśli używasz Perl v5.10 lub większy, można użyć \hdo poziomego spacji i \Rdla rodzajowego LINEBREAK, która obejmuje \r, \n, \r\n, \f, \cK, \x{2028}, i \x{2029}:

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

Będzie to działać na wszystkich plikach UTF-8 bez względu na ich łamanie linii, eliminując końcowe białe znaki (właściwość znaku Unicode HorizSpace), w tym nieprzyjemną PRZESTRZEŃ BEZ PRZERWU, która występuje przed łamaniem linii Unicode (włączając kombinacje CRLF) na końcu każdej linii.

Jest także o wiele bardziej przenośny niż wersja sed (1), ponieważ istnieje tylko jedna implementacja perla (1), ale wiele z sed (1).

Główny problem, jaki, jak widzę, nadal istnieje w przypadku find (1), ponieważ w niektórych naprawdę opornych systemach (wiesz, kim jesteś, AIX i Solaris) nie zrozumie -print0dyrektywy nadkrytycznej . Jeśli taka jest Twoja sytuacja, powinieneś po prostu użyć File::Findmodułu bezpośrednio z Perla i nie używać żadnych innych narzędzi Uniksa. Oto czysta wersja kodu Perla, która nie polega na niczym innym:

#!/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;

Jeśli korzystasz tylko z plików tekstowych ASCII lub ISO-8859-1, to dobrze, ale jeśli używasz plików ASCII lub UTF-8, dodaj -CSDprzełączniki w wywołaniu wewnętrznym Perla.

Jeśli masz mieszane kodowanie wszystkich trzech ASCII, ISO-8859-1 i UTF-8, obawiam się, że masz inny problem. :( Będziesz musiał ustalić kodowanie dla poszczególnych plików i nigdy nie ma dobrego sposobu, aby zgadnąć.

Biała spacja Unicode

Dla przypomnienia, Unicode ma 26 różnych białych znaków. Można skorzystać z unichars narzędzia wąchać te obecnie. Tylko pierwsze trzy poziome znaki białych znaków są prawie zawsze widoczne:

$ 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
źródło
0

GNU grep jest całkiem dobry w identyfikowaniu, czy plik jest binarny czy nie. Poza Solaris jestem pewien, że istnieją inne platformy, które nie są domyślnie instalowane z GNU grep, ale podobnie jak Solaris, jestem pewien, że możesz go zainstalować.

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

Jeśli jesteś w Solaris, można wymienić grepz /opt/csw/bin/ggrep.

Te grepflagi wykonaj następujące czynności: ltylko listy nazw dla plików pasujące, Rjest rekurencyjne, Ipasuje tylko pliki tekstowe (ignoruje pliki binarne) i Pjest dla składni wyrażeń regularnych Perl-kompatybilne.

Część Perla modyfikuje plik na miejscu, usuwając wszystkie spacje / tabulatory końcowe.

Wreszcie: jeśli UTF8 stanowi problem, odpowiedź tchrista w połączeniu z moją powinna być wystarczająca, pod warunkiem, że greptwoja kompilacja została zbudowana z obsługą UTF8 (zwykle opiekunowie pakietów starają się jednak zapewnić taką funkcjonalność).

Brian Vandenberg
źródło