grep: memória esgotada


42

Eu estava fazendo uma pesquisa muito simples:

grep -R Milledgeville ~/Documents

E depois de algum tempo esse erro apareceu:

grep: memory exhausted

Como posso evitar isso?

Eu tenho 10 GB de RAM no meu sistema e alguns aplicativos estão sendo executados, então estou realmente surpreso que um simples grep fique sem memória. ~/Documentstem cerca de 100 GB e contém todos os tipos de arquivos.

grep -RI pode não ter esse problema, mas também quero pesquisar em arquivos binários.

Respostas:


46

Dois problemas em potencial:

  • grep -R(exceto o GNU modificado grepencontrado no OS / X 10.8 e posterior) segue links simbólicos, portanto, mesmo que haja apenas 100 GB de arquivos ~/Documents, ainda pode haver um link simbólico, /por exemplo, e você acabará verificando todo o sistema de arquivos, incluindo arquivos gosto /dev/zero. Use grep -rcom o GNU mais recente grepou use a sintaxe padrão:

    find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
    

    (no entanto, observe que o status de saída não refletirá o fato de o padrão corresponder ou não).

  • greplocaliza as linhas que correspondem ao padrão. Para isso, ele precisa carregar uma linha de cada vez na memória. O GNU grep, ao contrário de muitas outras grepimplementações, não tem limite no tamanho das linhas que lê e suporta a pesquisa em arquivos binários. Portanto, se você tiver um arquivo com uma linha muito grande (ou seja, com dois caracteres de nova linha muito distantes), maior que a memória disponível, ele falhará.

    Isso normalmente aconteceria com um arquivo esparso. Você pode reproduzi-lo com:

    truncate -s200G some-file
    grep foo some-file
    

    Essa é difícil de contornar. Você poderia fazê-lo como (ainda com o GNU grep):

    find ~/Documents -type f -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} +
    

    Isso converte seqüências de caracteres NUL em um caractere de nova linha antes de alimentar a entrada grep. Isso cobriria os casos em que o problema ocorre devido a arquivos esparsos.

    Você pode otimizá-lo fazendo isso apenas para arquivos grandes:

    find ~/Documents -type f \( -size -100M -exec \
      grep -He Milledgeville {} + -o -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} + \)
    

    Se os arquivos não forem escassos e você tiver uma versão do GNU grepanterior 2.6, poderá usar a --mmapopção As linhas serão mapeadas na memória, em vez de copiadas, o que significa que o sistema sempre pode recuperar a memória paginando as páginas para o arquivo. Essa opção foi removida no GNU grep2.6


Na verdade, o GNU grep não se importa com a leitura em 1 linha, ele lê uma grande parte do arquivo em um único buffer. "Além disso, o GNU grep EVITE QUEBRAR A ENTRADA EM LINHAS." fonte: lists.freebsd.org/pipermail/freebsd-current/2010-August/...
Godric Seer

4
@GodricSeer, ele ainda pode ler uma grande parte do arquivo em um único buffer, mas se ele não encontrou a string lá e também não encontrou um caractere de nova linha, minha aposta é que ele mantenha esse buffer único na memória e lê o próximo buffer, pois ele deverá ser exibido se uma correspondência for encontrada. Então, o problema ainda é o mesmo. Na prática, um grep em um arquivo esparso de 200 GB falha com o OOM.
Stéphane Chazelas

1
@GodricSeer, bem, não. Se todas as linhas forem pequenas, você greppoderá descartar os buffers processados ​​até o momento. Você pode grepproduzir yesindefinidamente sem usar mais do que alguns kilobytes de memória. O problema é o tamanho das linhas.
Stéphane Chazelas

3
A --null-dataopção GNU grep também pode ser útil aqui. Força o uso de NUL em vez de nova linha como um terminador de linha de entrada.
iruvar 16/09/13

1
@ 1_CR, bom ponto, embora isso também defina o terminador da linha de saída como NUL.
Stéphane Chazelas

5

Eu costumo fazer

find ~/Documents | xargs grep -ne 'expression'

Tentei vários métodos e achei que era o mais rápido. Observe que isso não trata arquivos com espaços com o nome do arquivo muito bem. Se você sabe que esse é o caso e tem uma versão GNU do grep, você pode usar:

find ~/Documents -print0 | xargs -0 grep -ne 'expression'

Caso contrário, você pode usar:

 find ~/Documents -exec grep -ne 'expression' "{}" \;

Qual será execum grep para cada arquivo.


Isso quebrará em arquivos com espaços.
Chris Baixo

Hmm, isso é verdade.
Kotte

Você pode contornar isso comfind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan 10/09

@ChrisDown, em vez de uma solução não-protable do que uma solução portátil quebrada.
reto

@ChrisDown A maioria dos principais departamentos já adotou find -print0e xargs -0até agora: todos os três BSD, MINIX 3, Solaris 11, ...
Gilles 'SO- stop be evil'

4

Eu posso pensar em algumas maneiras de contornar isso:

  • Em vez de grepping todos os arquivos de uma vez, faça um arquivo de cada vez. Exemplo:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Se você precisar apenas saber quais arquivos contêm as palavras, faça-o grep -l. Como o grep interromperá a pesquisa após o primeiro hit, não será necessário continuar lendo nenhum arquivo enorme

  • Se você quiser o texto real também, poderá inserir dois greps separados:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    

O último exemplo não é uma sintaxe válida - você precisaria executar uma substituição de comando (e não deve fazê-lo, pois grepgera um delimitador legal nos nomes de arquivo). Você também precisa citar $file.
21313 Chris Down

O exemplo últimos sofre com o problema de nomes de arquivos com nova linha ou espaço em branco neles, (ele fará forpara processar o arquivo como dois argumentos)
Drav Sloan

@DravSloan Sua edição, embora seja uma melhoria, ainda quebra nos nomes de arquivos legais.
Chris Baixo

1
Sim, eu o deixei porque era parte da resposta dela, apenas tentei aprimorá-lo para que ele funcionasse (nos casos em que não há espaços / novas linhas etc. nos arquivos).
Drav Sloan

Correções dele -> ela, minhas desculpas Jenny: /
Drav Sloan 10/10

1

Estou esperando um disco de 6 TB para procurar dados perdidos e a memória está esgotada - erro. Isso deve funcionar para outros arquivos também.

A solução que encontramos foi ler o disco em pedaços usando dd e grepping os pedaços. Este é o código (big-grep.sh):

#problem: grep gives "memory exhausted" error on 6TB disks
#solution: read it on parts
if [ -z $2 ] || ! [ -e $1 ]; then echo "$0 file string|less -S # greps in chunks"; exit; fi

FILE="$1"
MATCH="$2"

SIZE=`ls -l $1|cut -d\  -f5`
CHUNKSIZE=$(( 1024 * 1024 * 1 )) 
CHUNKS=100 # greps in (100 + 1) x 1MB = 101MB chunks
COUNT=$(( $SIZE / $CHUNKSIZE * CHUNKS ))

for I in `seq 0 $COUNT`; do
  dd bs=$CHUNKSIZE skip=$(($I*$CHUNKS)) count=$(( $CHUNKS+1)) if=$FILE status=none|grep -UF -a --context 6 "$MATCH"
done

1
A menos que você leia pedaços sobrepostos , possivelmente perderá correspondências nos limites do pedaço. A sobreposição deve ser pelo menos tão grande quanto a string que você espera corresponder.
Kusalananda

Atualizado para pesquisar 1 MB extra em cada porção de 100 MB ...
Corte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.