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.
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:
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!!!
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...
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:
$ 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!!!
$ 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.