Grep recursivo vs find / -type f -exec grep {} \; Qual é mais eficiente / mais rápido?

70

Qual é mais eficiente para encontrar quais arquivos em um sistema de arquivos inteiro contêm uma sequência: grep recursivo ou encontrar com grep em uma instrução exec? Presumo que encontrar seria mais eficiente, porque você pode pelo menos fazer alguma filtragem se souber a extensão do arquivo ou um regex que corresponda ao nome do arquivo, mas quando você souber apenas o -type fque é melhor? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Exemplo:

grep -r -i 'the brown dog' /

find / -type f -exec grep -i 'the brown dog' {} \;

Gregg Leventhal
fonte
11
A eficiência da matemática / ciência da computação / algoritmo não é baseada em opiniões.
Gregg Leventhal
Verifique este. Embora não seja recursivo, daria um entendimento sobre o que é melhor. unix.stackexchange.com/questions/47983/…
Ramesh
8
@AvinashRaj, ele não está pedindo opinião. Ele está perguntando qual é mais eficiente e / ou mais rápido , e não qual é "melhor". Essa é uma pergunta perfeitamente respondível, com uma resposta única e específica que depende de como esses dois programas funcionam e do que exatamente você os fornece para pesquisar.
terdon
2
Observe que o -exec {} +formulário fará menos garfos, portanto deve ser mais rápido que -exec {} \;. Pode ser necessário adicionar -H(ou -h) às grepopções para obter uma saída exatamente equivalente.
Mikel
Você provavelmente não -rgrep
desejou

Respostas:

85

Não tenho certeza:

grep -r -i 'the brown dog' /*

é realmente o que você quis dizer. Isso significaria grep recursivamente em todos os arquivos e diretórios não ocultos /(mas ainda assim procure dentro de arquivos e diretórios ocultos).

Supondo que você quis dizer:

grep -r -i 'the brown dog' /

Algumas coisas a serem observadas:

  • Nem todas as grepimplementações suportam -r. E entre os que o fazem, os comportamentos diferem: alguns seguem links simbólicos para diretórios ao percorrer a árvore de diretórios (o que significa que você pode acabar procurando várias vezes no mesmo arquivo ou mesmo executar em loops infinitos), outros não. Alguns irão procurar dentro de arquivos de dispositivos (e isso levará algum tempo, /dev/zeropor exemplo) ou tubos ou arquivos binários ..., outros não.
  • É eficiente quando grepcomeça a procurar dentro de arquivos assim que os descobre. Mas enquanto ele aparece em um arquivo, não está mais procurando mais arquivos para pesquisar (o que provavelmente é tão bom na maioria dos casos)

Seu:

find / -type f -exec grep -i 'the brown dog' {} \;

(removido o -rque não fazia sentido aqui) é terrivelmente ineficiente porque você está executando um greppor arquivo. ;deve ser usado apenas para comandos que aceitam apenas um argumento. Além disso, aqui, como grepparece apenas em um arquivo, ele não imprimirá o nome do arquivo, portanto você não saberá onde estão as correspondências.

Você não está olhando dentro de arquivos de dispositivos, canais, links simbólicos ..., não está seguindo links simbólicos, mas ainda está potencialmente olhando dentro de coisas como /proc/mem.

find / -type f -exec grep -i 'the brown dog' {} +

seria muito melhor porque o grepmenor número possível de comandos seria executado. Você obteria o nome do arquivo, a menos que a última execução tenha apenas um arquivo. Para isso, é melhor usar:

find / -type f -exec grep -i 'the brown dog' /dev/null {} +

ou com GNU grep:

find / -type f -exec grep -Hi 'the brown dog' {} +

Observe que grepnão será iniciado até findencontrar arquivos suficientes para mastigar, portanto haverá um atraso inicial. E findnão continuará pesquisando por mais arquivos até que o anterior grepretorne. Alocar e passar a grande lista de arquivos tem algum impacto (provavelmente desprezível); portanto, em geral, será menos eficiente do que um grep -rque não segue o link simbólico ou olha para os dispositivos.

Com as ferramentas GNU:

find / -type f -print0 | xargs -r0 grep -Hi 'the brown dog'

Como acima, o menor número greppossível de instâncias será executado, mas findcontinuará procurando por mais arquivos enquanto a primeira grepchamada estiver dentro do primeiro lote. Isso pode ou não ser uma vantagem. Por exemplo, com os dados armazenados em discos rígidos rotacionais finde o grepacesso a dados armazenados em diferentes locais do disco, a velocidade do disco diminuirá a velocidade, fazendo com que a cabeça do disco se mova constantemente. Em uma configuração de RAID (onde finde greppode acessar discos diferentes) ou em SSDs, isso pode fazer uma diferença positiva.

Em uma configuração de RAID, a execução de várias chamadas simultâneas grep também pode melhorar as coisas. Ainda com as ferramentas GNU no armazenamento RAID1 com 3 discos,

find / -type f -print0 | xargs -r0 -P2 grep -Hi 'the brown dog'

pode aumentar significativamente o desempenho. Observe, no entanto, que o segundo grepserá iniciado apenas quando forem encontrados arquivos suficientes para preencher o primeiro grepcomando. Você pode adicionar uma -nopção xargspara que isso aconteça mais cedo (e passar menos arquivos por grepchamada).

Observe também que, se você estiver redirecionando a xargssaída para algo que não seja um dispositivo terminal, os grepss começarão a armazenar buffer em sua saída, o que significa que a saída desses greps provavelmente será intercalada incorretamente. Você precisaria usar stdbuf -oL(quando disponível, como no GNU ou FreeBSD) neles para solucionar isso (você ainda pode ter problemas com linhas muito longas (normalmente> 4KiB)) ou cada um deles escrever sua saída em um arquivo separado e concatená-los tudo no final.

Aqui, a string que você está procurando é fixa (não uma regexp), portanto, o uso da -Fopção pode fazer a diferença (improvável, pois as grepimplementações já sabem como otimizar isso).

Outra coisa que pode fazer uma grande diferença é fixar o código do idioma em C se você estiver em um código de idioma de vários bytes:

find / -type f -print0 | LC_ALL=C xargs -r0 -P2 grep -Hi 'the brown dog'

Para evitar olhar para dentro /proc, /sys..., use -xdeve especifique os sistemas de arquivos nos quais deseja pesquisar:

LC_ALL=C find / /home -xdev -type f -exec grep -i 'the brown dog' /dev/null {} +

Ou remova os caminhos que você deseja excluir explicitamente:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +
Stéphane Chazelas
fonte
Não acho que alguém possa me apontar um recurso - ou explicar - o que {} e + significam. Não há nada que eu possa ver nas páginas do manual para exec, grep ou encontrar na caixa Solaris que estou usando. É apenas o shell que concatena os nomes de arquivos e os passa para o grep?
3
@Poldie, que está claramente explicado na descrição do -execpredicado na página homem Solaris
Stéphane Chazelas
Ah sim. Eu não estava escapando do meu {char enquanto procurava na página de manual. Seu link é melhor; Acho as páginas de manual terríveis de ler.
11
RAID1 com 3 discos? Que estranho ...
tink
11
@tink, sim RAID1 está em 2 ou mais discos. Com 3 discos em comparação com 2 discos, você aumenta a redundância e lê o desempenho, enquanto o desempenho de gravação é aproximadamente o mesmo. Com 3 discos em oposição a 2, isso significa que você também pode corrigir erros, pois quando um pouco gira em uma das cópias, você pode dizer o que é certo verificando todas as 3 cópias enquanto estiver com 2 discos, não é possível realmente contar.
Stéphane Chazelas
13

Se o *na grepchamada não é importante para você, em seguida, a primeira deve ser mais eficiente como apenas uma instância grepé iniciada, e garfos não são livres. Na maioria dos casos, será mais rápido, mesmo *nos casos extremos, mas a classificação pode reverter isso.

Pode haver outro find- grepestruturas que funcionam melhor, especialmente com muitos arquivos pequenos. A leitura de grandes quantidades de entradas e inodes de arquivos ao mesmo tempo pode melhorar o desempenho da mídia rotativa.

Mas vamos dar uma olhada nas estatísticas do syscall:

encontrar

> strace -cf find . -type f -exec grep -i -r 'the brown dog' {} \;
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.86    0.883000        3619       244           wait4
  0.53    0.004809           1      9318      4658 open
  0.46    0.004165           1      6875           mmap
  0.28    0.002555           3       977       732 execve
  0.19    0.001677           2       980       735 stat
  0.15    0.001366           1      1966           mprotect
  0.09    0.000837           0      1820           read
  0.09    0.000784           0      5647           close
  0.07    0.000604           0      5215           fstat
  0.06    0.000537           1       493           munmap
  0.05    0.000465           2       244           clone
  0.04    0.000356           1       245       245 access
  0.03    0.000287           2       134           newfstatat
  0.03    0.000235           1       312           openat
  0.02    0.000193           0       743           brk
  0.01    0.000082           0       245           arch_prctl
  0.01    0.000050           0       134           getdents
  0.00    0.000045           0       245           futex
  0.00    0.000041           0       491           rt_sigaction
  0.00    0.000041           0       246           getrlimit
  0.00    0.000040           0       489       244 ioctl
  0.00    0.000038           0       591           fcntl
  0.00    0.000028           0       204       188 lseek
  0.00    0.000024           0       489           set_robust_list
  0.00    0.000013           0       245           rt_sigprocmask
  0.00    0.000012           0       245           set_tid_address
  0.00    0.000000           0         1           uname
  0.00    0.000000           0       245           fchdir
  0.00    0.000000           0         2         1 statfs
------ ----------- ----------- --------- --------- ----------------
100.00    0.902284                 39085      6803 total

somente grep

> strace -cf grep -r -i 'the brown dog' .
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 40.00    0.000304           2       134           getdents
 31.71    0.000241           0       533           read
 18.82    0.000143           0       319         6 openat
  4.08    0.000031           4         8           mprotect
  3.29    0.000025           0       199       193 lseek
  2.11    0.000016           0       401           close
  0.00    0.000000           0        38        19 open
  0.00    0.000000           0         6         3 stat
  0.00    0.000000           0       333           fstat
  0.00    0.000000           0        32           mmap
  0.00    0.000000           0         4           munmap
  0.00    0.000000           0         6           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0       245       244 ioctl
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0       471           fcntl
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           futex
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0       132           newfstatat
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.000760                  2871       466 total
Hauke ​​Laging
fonte
11
Na escala de procurar um sistema de arquivos inteiro, os garfos são insignificantes. E / S é o que você deseja reduzir.
Gilles 'SO- stop be evil'
Embora seja um erro do OP, a comparação está incorreta, você deve remover o -rsinalizador grepao usá-lo find. Você pode ver que ele procurou repetidamente os mesmos arquivos comparando o número opendisso.
Qwertzguy
11
@qwertzguy, não, o -rdeve ser inofensivo, pois as -type fgarantias de que nenhum dos argumentos são diretórios. As múltiplas open()s são mais prováveis para baixo para os outros arquivos abertos por grepem cada invocação (bibliotecas, dados de localização ...) (obrigado pela edição em minha resposta btw)
Stéphane Chazelas
5

Se você estiver em um SSD e o tempo de busca for insignificante, você pode usar o GNU paralelo:

find /path -type f | parallel --gnu --workdir "$PWD" -j 8 '
    grep -i -r 'the brown dog' {} 
'

Isso executará até 8 processos grep ao mesmo tempo, com base no que foi findencontrado.

Isso atrapalha uma unidade de disco rígido, mas um SSD deve lidar muito bem com ela.

Naftuli Kay
fonte
-1

Mais uma coisa a considerar sobre este é o seguinte.

Algum dos diretórios que o grep precisará percorrer recursivamente conterá mais arquivos do que a configuração de nofile do sistema ? (por exemplo, número de identificadores de arquivos abertos, o padrão é 1024 na maioria das distribuições Linux)

Se assim for, então encontrar é definitivamente o caminho a percorrer uma vez que certas versões do grep vai bombardear com uma lista de argumentos muito longa de erro quando se atinge um diretório com mais arquivos do que o máximo de arquivo aberto ajuste lida.

Apenas meus 2 ¢.

B.Kaatz
fonte
11
Por que grepexplodiria? Pelo menos com o GNU grep, se você der um caminho com trailing /e usá- -Rlo, ele simplesmente percorrerá os diretórios. O shell não vai expandir nada, a menos que você dê globs de shell. Portanto, no exemplo dado ( /*), apenas o conteúdo da /matéria, não as subpastas que serão simplesmente enumeradas por grep, não é passado como argumento do shell.
0xC0000022L
Bem, considerando que o OP estava perguntando sobre pesquisar recursivamente (por exemplo, "grep -r -i 'o cachorro marrom' / *"), eu vi o grep do GNU (pelo menos a versão 2.9) bombardear com: "- bash: / bin / grep: lista de argumentos muito longa "usando a pesquisa exata que o OP usou em um diretório que continha mais de 140.000 subdiretórios.
precisa saber é o seguinte