¿Cómo ejecuto este comando `find`, pero solo en archivos no binarios?

8

Quiero eliminar los espacios en blanco finales de todos los archivos en una jerarquía de directorios recursiva. Yo uso esto:

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

Esto funciona, pero también eliminará el "espacio en blanco" final de los archivos binarios que se encuentran, lo que no es deseable.

¿Cómo le digo findque evite ejecutar este comando en archivos binarios?

John Feminella
fuente
Los sistemas de archivos Unix no hacen distinción entre archivos "binarios" y "no binarios"; no hay forma de saber qué tipo de datos hay en el archivo sin mirar dentro de él.
Wooble
@Wooble: Eso es correcto, pero hay comandos como los fileque pueden inspeccionar los datos.
John Feminella

Respuestas:

4

Podría intentar usar el filecomando Unix para ayudar a identificar los archivos que no desea, pero creo que puede ser mejor si especifica explícitamente qué archivos desea golpear en lugar de aquellos que no desea.

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

para evitar atravesar archivos de control de origen, es posible que desee algo como

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

Es posible que necesite o no algunas de las barras diagonales inversas dependiendo de su caparazón.

Bert F
fuente
2
No sé sobre usted, pero todos nuestros archivos fuente de Java siempre están en UTF-8 estándar, por lo que el comando sed no siempre hará lo correcto con todos ellos. También tengo sistemas sin -iopción de sed . Es difícil escribir un comando de shell portátil, ¿no?
tchrist
4

Se puede hacer en la línea de comando.

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

La respuesta más simple y portátil es ejecutar esto:

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

Explico por qué a continuación, donde también muestro cómo hacerlo usando solo la línea de comando, así como también cómo tratar con archivos de texto trans-ASCII como ISO-8859-1 (Latin-1) y UTF-8, que a menudo tienen - Espacio en blanco ASCII en ellos.


El resto de la historia

El problema es que find (1) no es compatible con el -Toperador de prueba de archivo, ni reconoce las codificaciones si lo hiciera, lo cual es absolutamente necesario para detectar la codificación Unicode estándar de facto UTF-8.

Lo que podría hacer es ejecutar la lista de nombres de archivo a través de una capa que arroja archivos binarios. Por ejemplo

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

Sin embargo, ahora tiene problemas con el espacio en blanco en sus nombres de archivo, por lo que debe retrasar esto con una terminación nula:

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

Otra cosa que podría hacer es usar no findpero find2perl, ya que Perl -Tya lo comprende :

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

Y si desea que Perl asuma que sus archivos están en UTF-8, use

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

O puede guardar el script resultante en un archivo y editarlo. Realmente no debería ejecutar la -Tprueba de archivo en cualquier archivo antiguo, sino solo en aquellos que son archivos simples según lo determinado por primera vez -f. De lo contrario, corre el riesgo de abrir dispositivos especiales, bloqueo en fifos, etc.

Sin embargo, si va a hacer todo eso, también podría omitir sed (1) por completo. Por un lado, es más portátil, ya que la versión POSIX de sed (1) no comprende -i, mientras que todas las versiones de Perl sí. Las versiones recientes de sed se apropiaron con amor de la -iopción muy útil de Perl, donde aparece por primera vez.

Esto también te da la oportunidad de arreglar tu expresión regular también. Realmente debería usar un patrón que coincida con uno o más espacios en blanco horizontales finales, no solo con cero, o se ejecutará más lentamente debido a la copia innecesaria. Eso es esto:

 s/[ \t]*$//

debiera ser

 s/[ \t]+$//

Sin embargo, cómo lograr que sed (1) entienda que requiere una extensión que no sea POSIX, generalmente -Rpara sistemas System como Solaris o Linux, o -Epara BSD como OpenBSD o MacOS. Sospecho que es imposible bajo AIX. Sabes, es más fácil escribir un shell portátil que un script de shell portátil.

Advertencia sobre 0xA0

Aunque esos son los únicos caracteres de espacio en blanco horizontales en ASCII, tanto ISO-8859-1 como en consecuencia también Unicode tienen el ESPACIO SIN INTERRUPCIONES en el punto de código U + 00A0. Este es uno de los dos principales caracteres no ASCII que se encuentran en muchos corpus Unicode, y últimamente he visto romper el código regex de muchas personas porque lo olvidaron.

Entonces, ¿por qué no haces esto?

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

Si es posible que tenga archivos UTF-8 para hacer frente, complemento -CSD, y si está ejecutando Perl v5.10 o superior, se puede utilizar \hpara el espacio en blanco horizontal y \Rde un salto de línea genérico, que incluye \r, \n, \r\n, \f, \cK, \x{2028}, y \x{2029}:

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

Eso funcionará en todos los archivos UTF-8 sin importar sus saltos de línea, eliminando los espacios en blanco horizontales (propiedad de caracteres Unicode HorizSpace), incluido el molesto ESPACIO NO-BREAK que ocurre antes de un salto de línea Unicode (incluye combos CRLF) al final de cada línea.

También es mucho más portátil que la versión sed (1), porque solo hay una implementación perl (1), pero muchas de sed (1).

El principal problema que veo que queda allí es con find (1), ya que en algunos sistemas verdaderamente recalcitrantes (ya sabes quién eres, AIX y Solaris), no entenderá la -print0directiva supercrítica . Si esa es su situación, entonces debería usar el File::Findmódulo de Perl directamente y no usar otras utilidades de Unix. Aquí hay una versión pura de Perl de su código que no se basa en nada más:

#!/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 solo está ejecutando archivos de texto ASCII o ISO-8859-1, está bien, pero si está ejecutando archivos ASCII o UTF-8, agréguelos -CSDa los interruptores en la llamada interior a Perl.

Si tiene codificaciones mixtas de los tres ASCII, ISO-8859-1 y UTF-8, entonces me temo que tiene otro problema. :( Deberá averiguar la codificación por archivo, y nunca hay una buena manera de adivinar eso.

Espacio en blanco Unicode

Para el registro, Unicode tiene 26 caracteres de espacio en blanco diferentes. Puede usar la utilidad unichars para detectarlos . Solo se ven los primeros tres caracteres de espacio en blanco horizontales:

$ 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
fuente
0

GNU grep es bastante bueno para identificar si un archivo es binario o no. Aparte de Solaris, estoy seguro de que hay otras plataformas que no vienen con GNU grep instalado de forma predeterminada, pero como Solaris, estoy seguro de que puedes instalarlo.

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

Si está en Solaris, lo reemplazaría grepcon /opt/csw/bin/ggrep.

Los grepindicadores hacen lo siguiente: lsolo enumera nombres de archivos para archivos coincidentes, Res recursivo, Isolo coincide con archivos de texto (ignora archivos binarios) y Pes para sintaxis de expresión regular compatible con perl.

La parte perl modifica el archivo en el lugar, eliminando todos los espacios / pestañas finales.

Por último: si UTF8 es un problema, la respuesta de tchrist junto con la mía debería ser suficiente, siempre que la compilación de grepusted haya sido construida con soporte UTF8 (aunque los mantenedores de paquetes intentan proporcionar ese tipo de funcionalidad).

Brian Vandenberg
fuente