Como pesquisar arquivos onde existem duas palavras diferentes?

14

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

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

O problema que encontro é que, se não houver exatamente um espaço entre "FIND" e "ME", o resultado da pesquisa não produzirá o arquivo. Como adaptar a cadeia de pesquisa anterior, onde as palavras "FIND" e "ME existem em um arquivo em oposição a" FIND ME "?

Estou usando o AIX.

Chad Harrison
fonte
1
As palavras existem em algum lugar do arquivo ou estão sempre na mesma linha?
Sobrique
A intenção era a mesma linha.
Chad Harrison
Uma alternativa, se as palavras estão na mesma linha é usar uma expressão regular com grep -E/ egrepque descreve todos os padrões que você está interessado em (e usando +em vez de ;se seu achado tem suporte para +.
Matt Bianco

Respostas:

21

Com as ferramentas GNU:

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

Você pode fazer normalmente:

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 se grepainda ser portátil, enquanto permite qualquer caractere nos nomes de arquivo, você pode:

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 findpara um formato adequado para xargs (que espera um espaço em branco (SPC / TAB / NL e os outros espaços em branco do seu código do idioma com algumas implementações de xargs)) lista separada de palavras em que aspas simples e duplas e barras invertidas podem espaços em branco de escape e um ao outro).

Geralmente, você não pode pós-processar a saída de find -print, porque ela separa os nomes de arquivo com um caractere de nova linha e não escapa aos caracteres de nova linha encontrados nos nomes de arquivo. Por exemplo, se vemos:

./a
./b

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

Ao usar .//., porque //não pode aparecer de outra maneira no caminho de um arquivo como resultado de find(porque não existe um diretório com um nome vazio e /não é permitido em um nome de arquivo), sabemos que, se virmos uma linha que contenha //, isso será a primeira linha de um novo nome de arquivo. Portanto, podemos usar esse awkcomando para escapar de todos os caracteres de nova linha, exceto daqueles que precedem essas linhas.

Se pegarmos o exemplo acima, findseria exibido no primeiro caso (um arquivo):

.//a
./b

Qual awk escapa para:

.//a\
./b

Então, isso xargsé visto como um argumento. E no segundo caso (dois arquivos):

.//a
.//b

O awkque sairia como está, então xargsvê dois argumentos.

Stéphane Chazelas
fonte
Por que não usar find ... -print0e em grep --nullvez disso?
razzed
@ razzed, não sei o que você quer dizer com isso. grep --null(aka -Z) é usado no primeiro, mas é uma extensão GNU. -print0(outra extensão GNU) não ajudaria aqui.
Stéphane Chazelas
Obrigado. Gostaria de agrupar seu código de shell em um script que usa o diretório de pesquisa como argumento na linha de comando. Não tenho muita certeza do que .//.significa ainda, e me pergunto como posso modificar isso para aceitar um argumento da linha de comando, digamos $1?
Tim
Obrigado. No seu comando, é necessário usar -print0com finde -0com xargs?
Tim
@ Tim, não sei o que você quer dizer. Eu não uso find -print0em nenhum lugar na minha resposta.
Stéphane Chazelas
8

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

grep -l FIND `grep -l ME *`
user45529
fonte
ISTO precisa de mais votos !! Muito mais elegante que a resposta "aceita". Trabalhou para mim.
Roblogic
Acabei de grep -l CategoryLinearAxis `grep -l labelJsFunction *`procurar arquivos que tenham ambos os atributos. Que maneira perfeita de fazer isso. 1
WEBjuju
3

Com awkvocê 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 cxe cyconta para as linhas correspondentes FINDe respectivamente ME. No ENDbloco, se ambos os contadores> 0, ele imprime o FILENAME.
Isso seria mais rápido / mais 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}' {} +
don_crissti
fonte
2

Ou use egrep -eou grep -Eassim:

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

ou

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

O +make find (se suportado) adiciona vários nomes de arquivo (caminho) como argumentos ao comando que está sendo -execeditado. Isso salva processos e é muito mais rápido do que o \;que invoca o comando uma vez para cada arquivo encontrado.

-type f corresponde apenas aos arquivos, para evitar grepping em um diretório.

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

Adicione -ia ao grepcomando para torná-lo sem distinção entre maiúsculas e minúsculas.

Para corresponder apenas às linhas em que "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 linha por vez, o egrep é muito poderoso.

MattBianco
fonte
A maioria dos greps não suporta "-r"? Isso eliminaria a "localização", mas pode haver soquetes ou outros arquivos não comuns na árvore que estão sendo pesquisados.
stolenmoment
O OP usa o AIX e tinha findna pergunta.
21818 MattBianco
0

Olhando para a resposta aceita, parece mais complexa do que precisa ser. Versões GNU de finde grepe xargsapoio strings terminadas em nulo. É tão simples quanto:

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

Você pode modificar seu findcomando para filtrar os arquivos desejados e funciona com nomes de arquivos que contêm qualquer caractere; sem a complexidade adicional de sedanálise. Se você deseja processar ainda mais os arquivos, adicione outro --nullaté o últimogrep

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.

deslumbrado
fonte
1
--null, --print0, -0São todas as extensões GNU. Embora alguns deles sejam encontrados em outras implementações hoje em dia, eles ainda não são portáteis e não estão no padrão POSIX ou Unix.
Stéphane Chazelas