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.