De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.


Find + Xargs

Colaboração: Julio Cezar Neves

Data de Publicação: 16 de agosto de 2019

O comando find serve para procurar arquivos por diversas características. Não vou aqui explicar toda a sua sintaxe, que é muito extensa, mas vou dar uns macetes "matadores".

Vamos começar medindo de 3 formas distintas os tempos para pesquisar recursivamente quantos são os arquivos do meu diretório de fontes Shell que utilizam o comando date.

$ time find . -type f -exec grep -l date {} \; | wc -l 
621
real	0m1.397s
user	0m0.020s
sys	0m0.160s

$ time find . -type f -exec grep -l date {} + | wc -l 
621
real	0m0.027s
user	0m0.008s
sys	0m0.016s

$ time find . -type f | xargs grep -l date | wc -l
621
real	0m0.030s
user	0m0.008s
sys	0m0.028s

Garanto que é grande a chance de você usar a sintaxe mais lenta dessas três? :( Vamos analisar essas linhas:

Primeiro método:

O par de chaves ({}) no find é o alvo para onde serão mandados os arquivos que atenderam à(s) condição(ões) do comando e a contrabarra (\) antes do ponto e vírgula (;) serve para escondê-lo do Shell. Supondo que arq1, arq2 e arq3 atenderam à condição (-type f), o find montaria a seguinte linha:

$ grep -l date arq1 | wc -l ;grep -l date arq2 | wc -l ;grep -l date arq3 | wc -l ;

E mandaria para execução. Como eu tenho 1535 arquivos no meu diretório, o find mandou executar 1535 comandos grep. UFA!!!

Segundo método:

A simples substituição do ponto e vírgula (;) pelo sinal de adição (+) não gera essa montanha de grep e por isso é muito mais veloz. Se usarmos o mesmo exemplo do primeiro método a linha mandada para execução seria a seguinte:

$ grep -l date arq1 arq2 arq3 | wc -l

Ou seja, uma "grepada" só e por isso muito mais rápido. Nossa preocupação nesse caso é que se a linha a ser executada tiver mais parâmetros que o Bash pode suportar, você ganhará uma indefectível mensagem: Too many parameters...

Terceiro método:

O comando xargs por padrão pega o que vem da entrada primária e coloca atrás da instrução que ele está executando, fica monitorando a quantidade de parâmetros que está passando e se for exceder o limite, dá uma primeira executada com o que for possível e volta para executar o restante, se necessário diversas vezes, até que todos os parâmetros sejam processados. Assim sendo, ele atua como no segundo método, mas tem suas salvaguardas para não dar erro. Ele é ligeiramente mais lento porque o pipe (|) força um subshell e porque o xargs, por ser um comando externo, perde um tempo para a carga do seu código.

Este é o método que costumo usar, então vou esticar esse artigo pras bandas do xargs, exemplificando.

Olha isso:

$ seq 4 | xargs echo Linha        # Sempre coloca tudo no fim
Linha 1 2 3 4
$ seq 4 | xargs -n 1 echo Linha   # Limitei e uma palavra por vez
Linha 1
Linha 2
Linha 3
Linha 4
$ seq 4 | xargs -n 1 echo Linha > arq # Salvei
$ cat arq | xargs -n1             # Palavra, viu? Default do xargs é echo
Linha
1
Linha
2
Linha
3
Linha
4
$ cat arq | xargs -L2             # De duas em duas linhas. -L é linha
Linha 1 Linha 2
Linha 3 Linha 4

Para mudar o comportamento do xargs, mudando o parâmetro recebido para qualquer posição. Use a opção -i ou -I.

$ ls arq* | xargs -i bash -c "mv {} dir; echo Movi {}"
Movi arq
Movi arq.tmp
Movi arq.err

As opções -i e -I precisam de um alvo para onde mandar os dados oriundos da entrada primária (stdin). Com a opção -i podemos usar o default, que é {} com ambas, podemos especificar, mas isso na -I é mandatório.

Neste código que acabamos de ver usei um subterfúgio para contornar a restrição do xargs monitorar uma única instrução, usando para tal o comando bash -c que pode executar uma linha de comandos.

$ ls arq* | xargs -I ALVO bash -c "mv ALVO dir; echo Movi ALVO"
Movi arq
Movi arq.err
Movi arq.tmp

Nesse caso especifiquei a palavra ALVO como o alvo do xargs. Poderia ter feito o mesmo usando a opção -i.

E agora o exemplo matador: quero procurar recursivamente todos os arquivos cujos nomes possuam espaço em branco e tenham a cadeia xxxx no seu interior.

Mas antes observe o seguinte:

$ cat dir/nome\ ruim

Isso é um teste para encontrar interativamente a cadeia xxxx

Então no diretório dir existe um arquivo chamado nome ruim, que possui a cadeia xxxx. Vamos pesquisá-la então:

Tentativa 1:

$ find . -name '* *' | xargs grep -l xxxx
grep: ./dir/nome: Arquivo ou diretório não encontrado
grep: ruim: Arquivo ou diretório não encontrado

Deu zebra!!!

Tentativa 2:

$ find . -name '* *' -print0 | xargs -0 grep -l xxxx
./dir/nome ruim

Funfou!!!

Essa funcionou porque a ação -print0 do find manda para a saída os campos terminados por um caractere <NULL> (zeros binários) e não por nenhum caractere que compõe a variável $IFS (espaço, <TAB>, <ENTER>).

Como o maior parceiro do find é o xargs, este também tem a opção -0 (zero) para receber dados terminados por <NULL>, desta forma dando a resposta correta e não quebrando o nome do arquivo no espaço.

Error: No domain found