Comment exécuter cette commande `find`, mais uniquement sur des fichiers non binaires?

8

Je souhaite supprimer les espaces de fin de tous les fichiers d'une hiérarchie de répertoires récursifs. J'utilise ceci:

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

Cela fonctionne, mais supprimera également les "espaces blancs" de fin des fichiers binaires trouvés, ce qui n'est pas souhaitable.

Comment dire findpour éviter d'exécuter cette commande sur des fichiers binaires?

John Feminella
la source
Les systèmes de fichiers Unix ne font aucune distinction entre les fichiers "binaires" et "non binaires"; il n'y a aucun moyen de savoir quel type de données se trouve dans le fichier sans regarder à l'intérieur.
Wooble
@Wooble: C'est correct, mais il existe des commandes telles que celles filequi peuvent inspecter les données.
John Feminella

Réponses:

4

Vous pouvez essayer d'utiliser la filecommande Unix pour aider à identifier les fichiers que vous ne voulez pas, mais je pense que ce serait mieux si vous spécifiez explicitement quels fichiers vous voulez frapper plutôt que ceux que vous ne voulez pas.

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

pour éviter de traverser dans les fichiers de contrôle de source, vous voudrez peut-être quelque chose comme

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

Vous pouvez ou non avoir besoin de certaines barres obliques inverses en fonction de votre shell.

Bert F
la source
2
Je ne sais pas pour vous, mais tous nos fichiers source Java sont toujours en UTF-8 standard, de sorte que la commande sed ne fera pas toujours la bonne chose avec tout cela. J'ai également des systèmes sans -ioption pour sed . Il est difficile d'écrire une commande shell portable, n'est-ce pas?
tchrist
4

Cela peut être fait sur la ligne de commande.

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

La réponse la plus simple et la plus portable consiste à exécuter ceci:

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

J'explique pourquoi ci-dessous, où je montre également comment le faire en utilisant uniquement la ligne de commande, ainsi que la façon de traiter les fichiers texte trans-ASCII comme ISO-8859-1 (Latin-1) et UTF-8, qui ont souvent non -ASCII espace blanc en eux.


Le reste de l'histoire

Le problème est que find (1) ne prend pas en charge l' -Topérateur filetest, ni ne reconnaît les encodages si c'est le cas - ce dont vous avez absolument besoin pour détecter UTF-8, l'encodage Unicode standard de facto.

Ce que vous pourriez faire, c'est d'exécuter la liste des noms de fichiers à travers une couche qui jette des fichiers binaires. Par exemple

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

Cependant, vous avez maintenant des problèmes avec les espaces dans vos noms de fichiers, vous devez donc retarder cela avec une terminaison nulle:

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

Une autre chose que vous pourriez faire est de ne pas l'utiliser findmais find2perl, puisque Perl comprend -Tdéjà:

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

Et si vous voulez que Perl suppose que ses fichiers sont en UTF-8, utilisez

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

Ou vous pouvez enregistrer le script résultant dans un fichier et le modifier. Vous ne devriez pas vraiment exécuter le -Tfiletest sur n'importe quel ancien fichier, mais plutôt uniquement sur ceux qui sont des fichiers simples comme déterminé par -f. Sinon, vous risquez d'ouvrir des offres spéciales sur les appareils, de bloquer sur fifos, etc.

Cependant, si vous allez faire tout cela, vous pourriez tout aussi bien ignorer sed (1). D'une part, il est plus portable, car la version POSIX de sed (1) ne comprend pas -i, contrairement à toutes les versions de Perl. Les dernières versions de sed se sont approprié avec amour l' -ioption très utile de Perl où ti apparaît pour la première fois.

Cela vous donne également la possibilité de corriger votre regex, aussi. Vous devez vraiment utiliser un modèle qui correspond à un ou plusieurs espaces horizontaux de fin, et pas seulement à zéro, sinon vous exécuterez plus lentement en cas de copie inutile. C'est ça:

 s/[ \t]*$//

devrait être

 s/[ \t]+$//

Cependant, comment comprendre sed (1) qui nécessite une extension non POSIX, généralement soit -Rpour les Unités System System comme Solaris ou Linux, soit -Epour celles BSD comme OpenBSD ou MacOS. Je soupçonne que c'est impossible sous AIX. Il est hélas plus facile d'écrire un shell portable qu'un script shell portable, vous savez.

Avertissement sur 0xA0

Bien que ce soient les seuls caractères d'espace blanc horizontaux en ASCII, ISO-8859-1 et par conséquent également Unicode ont le NO-BREAK SPACE au point de code U + 00A0. C'est l'un des deux premiers caractères non ASCII trouvés dans de nombreux corpus Unicode, et j'ai récemment vu beaucoup de codes regex casser car ils l'ont oublié.

Alors, pourquoi ne faites-vous pas cela:

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

Si vous pouvez avoir des fichiers UTF-8 à traiter, ajouter -CSD, et si vous utilisez Perl v5.10 ou plus, vous pouvez utiliser \hpour un espace horizontal et \Run saut de ligne générique, qui comprend \r, \n, \r\n, \f, \cK, \x{2028}et \x{2029}:

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

Cela fonctionnera sur tous les fichiers UTF-8, quels que soient leurs sauts de ligne, en supprimant les espaces horizontaux de fin (propriété de caractère Unicode HorizSpace), y compris l'ESPACE NO-BREAK ESPACE qui se produit avant un saut de ligne Unicode (y compris les combos CRLF) à la fin de chaque ligne.

Elle est également beaucoup plus portable que la version sed (1), car il n'y a qu'une seule implémentation perl (1), mais beaucoup de sed (1).

Le principal problème que je vois y rester est avec find (1), car sur certains systèmes vraiment récalcitrants (vous savez qui vous êtes, AIX et Solaris), il ne comprendra pas la -print0directive supercritique . Si tel est votre cas, vous devez simplement utiliser File::Finddirectement le module de Perl et n'utiliser aucun autre utilitaire Unix. Voici une version pure Perl de votre code qui ne repose sur rien d'autre:

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

Si vous utilisez uniquement des fichiers texte ASCII ou ISO-8859-1, c'est bien, mais si vous utilisez des fichiers ASCII ou UTF-8, ajoutez-les -CSDaux commutateurs de l'appel intérieur de Perl.

Si vous avez des encodages mixtes des trois ASCII, ISO-8859-1 et UTF-8, je crains que vous ayez un autre problème. :( Vous devrez comprendre l'encodage fichier par fichier, et il n'y a jamais de bon moyen de le deviner.

Espace Unicode

Pour mémoire, Unicode possède 26 espaces différents. Vous pouvez utiliser l' utilitaire unichars pour les détecter. Seuls les trois premiers caractères blancs horizontaux sont presque jamais vus:

$ 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
la source
0

GNU grep est assez bon pour identifier si un fichier est binaire ou non. À part Solaris, je suis sûr qu'il existe d'autres plates-formes qui ne sont pas fournies avec GNU grep installé par défaut, mais comme Solaris, je suis sûr que vous pouvez l'installer.

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

Si vous êtes dans Solaris, vous devez le remplacer greppar /opt/csw/bin/ggrep.

Les grepindicateurs font ce qui suit: lne répertorie que les noms de fichiers pour les fichiers correspondants, Rest récursif, Icorrespond uniquement aux fichiers texte (ignore les fichiers binaires) et Pest pour la syntaxe des expressions régulières compatible Perl.

La partie perl modifie le fichier sur place, supprimant tous les espaces / tabulations de fin.

Enfin: si UTF8 est un problème, la réponse de tchrist couplée à la mienne devrait être suffisante, à condition que la version que grepvous avez soit construite avec le support UTF8 (cependant, les responsables de paquets essaient généralement de fournir ce type de fonctionnalité).

Brian Vandenberg
la source