Como pesquisar arquivos onde existem duas palavras diferentes?

11

Estou procurando uma maneira de pesquisar arquivos em que existam duas instâncias de palavra no mesmo arquivo. Eu tenho usado o seguinte para realizar minhas pesquisas até este ponto:

find . -exec grep -l "FIND ME" {} \;

O problema que estou correndo é que, se não houver exatamente um espaço entre "FIND" e "ME", o resultado da pesquisa não produzirá o arquivo. Como eu adapto a antiga string de busca onde ambas as palavras "FIND" e "ME existem em um arquivo ao invés de" FIND ME "?

Estou usando o AIX.

    
por Chad Harrison 13.03.2013 / 16:01

5 respostas

18

Com ferramentas GNU:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Você pode fazer isso de maneira padrão:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Mas isso executaria dois greps por arquivo. Para evitar a execução de muitos grep s e ainda ser portável enquanto ainda permite qualquer caractere em nomes de arquivos, você poderia fazer:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

A ideia é converter a saída de find em um formato adequado para xargs (que espera uma lista separada em branco (SPC / TAB / NL, e os outros espaços em branco de sua localidade com algumas implementações de xargs ) palavras onde simples, aspas duplas e barras invertidas podem escapar de espaços em branco e um ao outro).

Geralmente, não é possível pós-processar a saída de find -print , porque ela separa os nomes de arquivos com um caractere de nova linha e não escapa dos caracteres de nova linha encontrados nos nomes dos arquivos. Por exemplo, se vemos:

./a
./b

Não temos como saber se é um arquivo chamado b em um diretório chamado a<NL>. ou se são os dois arquivos a e b .

Usando .//. , porque // não pode aparecer em um caminho de arquivo como saída por find (porque não existe um diretório com um nome vazio e / não é permitido em um nome de arquivo ), sabemos que, se vemos uma linha que contém // , essa é a primeira linha de um novo nome de arquivo. Portanto, podemos usar esse comando awk para escapar de todos os caracteres de nova linha, mas daqueles que precedem essas linhas.

Se pegarmos o exemplo acima, find produziria no primeiro caso (um arquivo):

.//a
./b

Qual awk escapa para:

.//a\
./b

Para que xargs o veja como um argumento. E no segundo caso (dois arquivos):

.//a
.//b

Qual awk deixaria como está, então xargs vê dois argumentos.

    
por 13.03.2013 / 19:35
3

Com awk você também pode executar:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Ele usa cx e cy para contar as linhas que correspondem a FIND e, respectivamente, ME . No bloco END , se os dois contadores > 0, imprime o FILENAME .
Isso seria mais rápido / eficiente com gnu awk :

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
    
por 21.07.2015 / 12:56
3

Se os arquivos estiverem em um único diretório e o nome deles não contiver espaço, guia, nova linha, * , ? nor [ caracteres e não começar com - nem . , isso irá obter uma lista de arquivos contendo ME, e então diminuirá para aqueles que também contêm FIND.

grep -l FIND 'grep -l ME *'
    
por 20.08.2013 / 18:55
2

Ou use egrep -e ou grep -E da seguinte forma:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

ou

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

O + faz com que o find (se suportado) inclua vários nomes de arquivos (path) como argumentos para o comando que está sendo -exec ed. Isso salva processos e é muito mais rápido que \; , o que invoca o comando uma vez para cada arquivo encontrado.

-type f corresponde apenas a arquivos, para evitar erros em um diretório.

'(ME.*FIND|FIND.*ME)' é uma expressão regular que corresponde a qualquer linha que contenha "ME", seguida de "FIND" ou "FIND", seguida de "ME". (aspas simples para impedir que o shell interprete caracteres especiais).

Adicione um -i ao comando grep para torná-lo sem distinção entre maiúsculas e minúsculas.

Para corresponder apenas às linhas nas quais "FIND" vem antes de "ME", use 'FIND.*ME' .

Para exigir espaços (1 ou mais, mas nada mais) entre as palavras: 'FIND +ME'

Para permitir espaços (0 ou mais, mas nada mais) entre as palavras: 'FIND *ME'

As combinações são infinitas com expressões regulares, e desde que você esteja interessado em corresponder apenas em uma linha por vez, o egrep é muito poderoso.

    
por 22.09.2017 / 13:21
0

Olhando para a resposta aceita, parece mais complexo do que precisa ser. As versões GNU de find e grep e xargs suportam sequências terminadas em NULL. É tão simples como:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Você pode modificar seu comando find para filtrar os arquivos desejados, e ele funciona com nomes de arquivos contendo qualquer caractere; sem a complexidade adicional de sed parsing. Se você quiser continuar processando os arquivos, adicione outro --null ao último grep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

E, como uma função:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Obviamente, use a resposta aceita se você não estiver executando versões GNU dessas ferramentas.

    
por 28.06.2017 / 17:42