Como executo esse comando `find`, mas apenas em arquivos não binários?

8

Quero remover o espaço em branco à direita de todos os arquivos em uma hierarquia de diretórios recursiva. Eu uso isso:

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

Isso funciona, mas também remove o "espaço em branco" à direita dos arquivos binários encontrados, o que é indesejável.

Como eu digo findpara evitar executar este comando em arquivos binários?

John Feminella
fonte
Os sistemas de arquivos Unix não fazem distinção entre arquivos "binários" e "não binários"; não há como saber que tipo de dados estão no arquivo sem olhar dentro dele.
Wooble
@ Wooble: Isso está correto, mas existem comandos como os fileque podem inspecionar os dados.
John Augella 14:11

Respostas:

4

Você pode tentar usar o filecomando Unix para ajudar a identificar os arquivos que não deseja, mas acho que será melhor se você especificar explicitamente quais arquivos deseja acessar e não aqueles que não deseja.

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

Para evitar passar para os arquivos de controle de origem, você pode querer algo como

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

Você pode ou não precisar de algumas barras invertidas, dependendo do seu shell.

Bert F
fonte
2
Eu não sei sobre você, mas todos os nossos arquivos de origem Java estão sempre no padrão UTF-8, portanto, o comando sed nem sempre fará a coisa certa com todos eles. Eu também tenho sistemas sem uma -iopção para sed . É difícil escrever um comando de shell portátil, não é?
tchrist
4

Isso pode ser feito na linha de comando.

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

A resposta mais simples e mais portátil é executar o seguinte:

#!/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 que abaixo, onde também mostro como fazê-lo usando apenas a linha de comando e como lidar com arquivos de texto trans-ASCII como ISO-8859-1 (Latin-1) e UTF-8, que depois não possuem -ASCII em branco neles.


O resto da história

O problema é que o find (1) não suporta o -Toperador de teste de arquivo nem reconhece as codificações - caso necessário - o que é absolutamente necessário para detectar a codificação Unicode padrão de fato UTF-8.

O que você pode fazer é executar a lista de nomes de arquivos através de uma camada que gera arquivos binários. Por exemplo

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

No entanto, agora você tem problemas com espaço em branco nos seus nomes de arquivos, portanto, é necessário atrasá-lo com terminação nula:

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

Outra coisa que você poderia fazer é usar não , findmas find2perljá que Perl -Tjá entende :

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

E se você quiser que o Perl assuma que seus arquivos estão em UTF-8, use

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

Ou você pode salvar o script resultante em um arquivo e editá-lo. Você realmente não deve apenas executar o -Tteste de arquivo em qualquer arquivo antigo, mas apenas naqueles que são arquivos simples, conforme determinado pela primeira vez -f. Caso contrário, você corre o risco de abrir promoções do dispositivo, bloquear fifos etc.

No entanto, se você fizer tudo isso, poderá ignorar completamente o sed (1). Por um lado, é mais portátil, pois a versão POSIX do sed (1) não entende -i, enquanto todas as versões do Perl o fazem. As versões dos últimos dias do sed apropriadamente se apropriaram da -iopção muito útil do Perl, onde ti aparece pela primeira vez.

Isso também oferece a oportunidade de corrigir seu regex também. Você realmente deve usar um padrão que corresponda a um ou mais espaços em branco horizontais à direita, não apenas a zero, ou será mais lento se copiar desnecessariamente. Ou seja, isso:

 s/[ \t]*$//

deveria estar

 s/[ \t]+$//

No entanto, como fazer com que o sed (1) entenda que requer uma extensão não POSIX, geralmente -Rpara os System Ⅴ Unices como Solaris ou Linux, ou -Epara os BSD como OpenBSD ou MacOS. Eu suspeito que é impossível no AIX. É quase mais fácil escrever um shell portátil do que um script de shell portátil, você sabe.

Aviso em 0xA0

Embora esses sejam os únicos caracteres de espaço em branco horizontal no ASCII, tanto o ISO-8859-1 quanto o Unicode também possuem o NO-BREAK SPACE no ponto de código U + 00A0. Esse é um dos dois principais caracteres não ASCII encontrados em muitos corpora Unicode e, ultimamente, tenho visto muitas pessoas quebrando o código regex porque se esqueceram disso.

Então, por que você não faz isso:

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

Se você pode ter arquivos UTF-8 para lidar com, add -CSD, e se você estiver executando Perl V5.10 ou superior, você pode usar \hpara o espaço em branco horizontal e \Rpor uma quebra de linha genérica, que inclui \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*$)//'

Isso funcionará em todos os arquivos UTF-8, independentemente de quebras de linha, eliminando o espaço em branco horizontal à direita (propriedade de caractere Unicode HorizSpace), incluindo o incômodo NO-BREAK SPACE que ocorre antes de uma quebra de linha Unicode (incluindo combos de CRLF) no final de cada linha.

Também é muito mais portátil que a versão sed (1), porque há apenas uma implementação perl (1), mas muitas da sed (1).

O principal problema que vejo ainda existe com o find (1), já que em alguns sistemas verdadeiramente recalcitrantes (você sabe quem você é, AIX e Solaris), ele não entende a -print0diretiva supercrítica . Se essa é a sua situação, você deve apenas usar o File::Findmódulo diretamente do Perl e não usar outros utilitários Unix. Aqui está uma versão Perl pura do seu código que não depende de mais nada:

#!/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 você estiver executando apenas arquivos de texto ASCII ou ISO-8859-1, tudo bem, mas se estiver executando com arquivos ASCII ou UTF-8, adicione -CSDaos comutadores na chamada interna para Perl.

Se você tiver codificações mistas dos três ASCII, ISO-8859-1 e UTF-8, receio que você tenha outro problema. :( Você precisará descobrir a codificação por arquivo e nunca há uma boa maneira de adivinhar isso.

Espaço em branco Unicode

Para o registro, o Unicode possui 26 caracteres de espaço em branco diferentes. Você pode usar o utilitário unichars para detectá -los. Somente os três primeiros caracteres de espaço em branco horizontal são quase sempre vistos:

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

O GNU grep é muito bom para identificar se um arquivo é binário ou não. Além do Solaris, tenho certeza de que existem outras plataformas que não vêm com o GNU grep instalado por padrão, mas, como o Solaris, tenho certeza de que você pode instalá-lo.

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

Se você estiver no Solaris, substituirá greppor /opt/csw/bin/ggrep.

Os grepsinalizadores fazem o seguinte: llista apenas nomes de arquivos para arquivos correspondentes, Ré recursivo, Icorresponde apenas a arquivos de texto (ignora arquivos binários) e Pé para sintaxe de expressão regular compatível com perl.

A parte perl modifica o arquivo no local, excluindo todos os espaços / guias finais.

Por fim: se o UTF8 for um problema, a resposta da tchrist juntamente com a minha deve ser suficiente, desde que a sua versão greptenha sido construída com o suporte ao UTF8 (normalmente, os mantenedores de pacotes tentam fornecer esse tipo de funcionalidade).

Brian Vandenberg
fonte