Come posso eseguire questo comando `find`, ma solo su file non binari?


8

Voglio rimuovere gli spazi vuoti finali da tutti i file in una gerarchia di directory ricorsiva. Io uso questo:

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

Funziona, ma rimuoverà anche lo "spazio bianco" finale dai file binari che si trovano, il che è indesiderabile.

Come posso findevitare di eseguire questo comando su file binari?


I filesystem Unix non fanno distinzione tra file "binari" e "non binari"; non c'è modo di dire quale tipo di dati è nel file senza guardarci dentro.
Wooble,

@Wooble: è corretto, ma ci sono comandi come quelli fileche possono ispezionare i dati.
John Feminella,

Risposte:


4

Potresti provare a usare il filecomando Unix per aiutare a identificare i file che non vuoi, ma penso che potrebbe essere meglio se specifichi esplicitamente quali file vuoi colpire piuttosto che quelli che non vuoi.

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

per evitare di spostarti nei file di controllo del codice sorgente potresti voler qualcosa di simile

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

Potresti aver bisogno o meno di alcune barre rovesciate a seconda della shell.


2
Non so te, ma tutti i nostri file sorgente Java sono sempre in UTF-8 standard, quindi quel comando sed non farà sempre la cosa giusta con tutti quelli. Ho anche sistemi senza -iun'opzione per sed . È difficile scrivere un comando di shell portatile, no?
tchrist,

4

Può essere fatto dalla riga di comando.

$ find . -type f -print|xargs file|grep ASCII|cut -d: -f1|xargs sed 's/[ \t]*$//' -i

3

La risposta più semplice e portatile è eseguire questo:

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

Spiego perché di seguito, dove mostro anche come farlo utilizzando solo la riga di comando, nonché come gestire file di testo trans-ASCII come ISO-8859-1 (Latin-1) e UTF-8, che spesso non hanno -ASCII spazi bianchi in essi.


Il resto della storia

Il problema è che find (1) non supporta l' -Toperatore filetest, né riconosce le codifiche in caso affermativo, che è assolutamente necessario rilevare UTF-8, codifica Unicode di fatto standard.

Quello che potresti fare è eseguire l'elenco dei nomi di file attraverso un livello che elimina i file binari. Per esempio

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

Tuttavia ora hai problemi con gli spazi bianchi nei nomi dei file, quindi devi ritardare con una terminazione nulla:

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

Un'altra cosa che potresti fare è usare non findma find2perl, poiché Perl comprende -Tgià:

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

E se vuoi che Perl presuma che i suoi file siano in UTF-8, usa

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

Oppure potresti salvare lo script risultante in un file e modificarlo. Dovresti davvero non solo eseguire il -Tfiletest su qualsiasi vecchio file, ma piuttosto solo su quelli che sono file semplici come da prima determinato da -f. Altrimenti rischi di aprire speciali dispositivi, bloccare su Fifos, ecc.

Tuttavia, se hai intenzione di fare tutto ciò, potresti anche saltare sed (1) del tutto. Per prima cosa, è più portatile, poiché la versione POSIX di sed (1) non capisce -i, mentre tutte le versioni di Perl lo fanno. Le versioni degli ultimi giorni di sed si sono appropriate amorevolmente -idell'opzione molto utile di Perl dove appare per la prima volta.

Questo ti dà anche l'opportunità di correggere anche la tua regex. Dovresti davvero usare un modello che corrisponda a uno o più spazi vuoti orizzontali finali, non solo a zero di essi, o correrai più lentamente dalla copia non necessaria. Cioè, questo:

 s/[ \t]*$//

dovrebbe essere

 s/[ \t]+$//

Tuttavia, come far capire a sed (1) che richiede un'estensione non POSIX, di solito sia -Rper System Ⅴ Unices come Solaris o Linux, sia -Eper quelli BSD come OpenBSD o MacOS. Ho il sospetto che sia impossibile sotto AIX. È purtroppo più facile scrivere una shell portatile che uno script di shell portatile, lo sai.

Avviso su 0xA0

Sebbene questi siano gli unici caratteri orizzontali dello spazio bianco in ASCII, sia ISO-8859-1 che, di conseguenza, anche Unicode hanno lo NO-BREAK SPACE al punto di codice U + 00A0. Questo è uno dei primi due caratteri non ASCII trovati in molti corpora Unicode, e ultimamente ho visto molte persone rompere il codice regex perché se ne sono dimenticate.

Quindi perché non lo fai e basta:

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

Se si potrebbe avere i file UTF-8 da affrontare, aggiungere -CSD, e se si esegue Perl v5.10 o superiore, è possibile utilizzare \hper spazi orizzontali e \Rper un'interruzione di linea generica, che comprende \r, \n, \r\n, \f, \cK, \x{2028}, e \x{2029}:

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

Funzionerà su tutti i file UTF-8 indipendentemente dalle interruzioni di riga, eliminando lo spazio bianco orizzontale finale (proprietà del carattere Unicode HorizSpace) incluso il fastidioso NO-BREAK SPACE che si verifica prima di un'interruzione di riga Unicode (include le combinazioni CRLF) alla fine di ogni riga.

È anche molto più portatile della versione sed (1), perché esiste solo un'implementazione perl (1), ma molti di sed (1).

Il problema principale che vedo rimanere lì è con find (1), poiché su alcuni sistemi veramente recalcitranti (sai chi sei, AIX e Solaris), non capirà la -print0direttiva supercritica . Se questa è la tua situazione, allora dovresti semplicemente usare il File::Findmodulo direttamente da Perl e non usare altre utility Unix. Ecco una versione pura del tuo codice Perl che non si basa su nient'altro:

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

Se stai eseguendo solo file di testo ASCII o ISO-8859-1, va bene, ma se stai eseguendo file ASCII o UTF-8, aggiungi -CSDagli switch nella chiamata interna a Perl.

Se hai codifiche miste di tutte e tre le ASCII, ISO-8859-1 e UTF-8, temo che tu abbia un altro problema. :( Dovrai capire la codifica in base al file e non c'è mai un buon modo per indovinarlo.

Unicode Whitespace

Per la cronaca, Unicode ha 26 diversi caratteri di spazi bianchi. Puoi usare l' utilità unichars per annusarli. Solo i primi tre caratteri orizzontali bianchi sono quasi mai visti:

$ 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 è abbastanza bravo a identificare se un file è binario o meno. Oltre a Solaris sono sicuro che ci sono altre piattaforme che non hanno GNU grep installato per impostazione predefinita, ma come Solaris sono sicuro che puoi installarlo.

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

Se sei in Solaris, lo sostituiresti grepcon /opt/csw/bin/ggrep.

I grepflag fanno quanto segue: lelenca solo i nomi dei file per i file corrispondenti, Rè ricorsivo, Icorrisponde solo ai file di testo (ignora i file binari) ed Pè per la sintassi delle espressioni regolari compatibile con perl.

La parte perl modifica il file sul posto, eliminando tutti gli spazi / schede finali.

Infine: se UTF8 è un problema, la risposta di tchrist unita alla mia dovrebbe essere sufficiente, a condizione che la build di grepte sia stata costruita con il supporto UTF8 (di solito i manutentori dei pacchetti cercano di fornire quel tipo di funzionalità).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.