Como executar o grep com vários padrões AND?

86

Gostaria de obter a correspondência de vários padrões com AND implícito entre padrões, ou seja, equivalente à execução de vários greps em uma sequência:

grep pattern1 | grep pattern2 | ...

Então, como convertê-lo em algo como?

grep pattern1 & pattern2 & pattern3

Eu gostaria de usar o grep único porque estou criando argumentos dinamicamente, para que tudo tenha que caber em uma sequência. Usar filtro é um recurso do sistema, não um grep, portanto não é um argumento para isso.


Não confunda esta pergunta com:

grep "pattern1\|pattern2\|..."

Esta é uma correspondência de vários padrões OU .

greenoldman
fonte

Respostas:

79

agrep pode fazer isso com esta sintaxe:

agrep 'pattern1;pattern2'

Com o GNU grep, quando criado com suporte para PCRE, você pode:

grep -P '^(?=.*pattern1)(?=.*pattern2)'

Com astgrep :

grep -X '.*pattern1.*&.*pattern2.*'

(adicionando .*s como <x>&<y>casa cadeias que correspondem tanto <x>e <y> exatamente , a&bnunca correspondem como não há tal string que pode ser tanto ae bao mesmo tempo).

Se os padrões não se sobrepuserem, você também poderá:

grep -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

A melhor maneira portátil é provavelmente awkcomo já mencionado:

awk '/pattern1/ && /pattern2/'

Com sed:

sed -e '/pattern1/!d' -e '/pattern2/!d'

Lembre-se de que todos terão sintaxe de expressão regular diferente.

Stéphane Chazelas
fonte
1
A agrepsintaxe não está funcionando para mim ... em qual versão foi introduzida?
Raman
O @Raman 2.04 de 1992 já o possuía. Não tenho motivos para acreditar que não estava lá desde o início. Versões mais recentes (após 1992) agreppodem ser encontradas incluídas no glimpse / webglimpse . Possivelmente você tem uma implementação diferente. No entanto, tive um erro na versão ast-grep, a opção para regexps aumentados é -X, não -A.
Stéphane Chazelas
@ StéphaneChazelas Obrigado, eu tenho o agrep0.8.0 no Fedora 23. Parece ser diferente do agrepque você mencionou.
Raman
1
@Raman, o seu soa como TREagrep .
Stéphane Chazelas
2
@Techiee, or just #awk '/p1/ && /p2/ {n++}; END {print 0+n}'
Stéphane Chazelas
19

Você não especificou a versão grep, isso é importante. Alguns mecanismos regexp permitem várias correspondências agrupadas por AND usando '&', mas esse é um recurso não padrão e não portátil. Mas, pelo menos, o GNU grep não suporta isso.

OTOH, você pode simplesmente substituir grep por sed, awk, perl, etc. (listados em ordem de aumento de peso). Com awk, o comando pareceria

awk '/ regexp1 / && / regexp2 / && / regexp3 / {print; } '

e pode ser construído para ser especificado na linha de comando de maneira fácil.

Netch
fonte
3
Lembre-se de que awkusa EREs, por exemplo, o equivalente a grep -E, em oposição aos BREs que a planície grepusa.
jw013
3
awkAs expressões regulares são chamadas de EREs, mas na verdade são um pouco idiossincráticas. Provavelmente, existem mais detalhes do que qualquer um se importa: wiki.alpinelinux.org/wiki/Regex
dubiousjim
Obrigado, grep 2.7.3 (openSUSE). Eu votei com você, mas vou manter a pergunta em aberto por um tempo, talvez haja algum truque para grep (não que eu não goste awk- simplesmente saber mais é melhor).
greenoldman
2
A ação padrão é imprimir a linha correspondente para que a { print; }peça não seja realmente necessária ou útil aqui.
Tripleee
7

Se patternscontiver um padrão por linha, você poderá fazer algo assim:

awk 'NR==FNR{a[$0];next}{for(i in a)if($0!~i)next}1' patterns -

Ou isso corresponde a substrings em vez de expressões regulares:

awk 'NR==FNR{a[$0];next}{for(i in a)if(!index($0,i))next}1' patterns -

Para imprimir todos em vez de há linhas de entrada no caso que patternsestá vazio, substitua NR==FNRcom FILENAME==ARGV[1], ou com ARGIND==1no gawk.

Essas funções imprimem as linhas de STDIN que contêm cada sequência especificada como argumento como uma substring. gasignifica grep all e gaiignora case.

ga(){ awk 'FILENAME==ARGV[1]{a[$0];next}{for(i in a)if(!index($0,i))next}1' <(printf %s\\n "$@") -; }
gai(){ awk 'FILENAME==ARGV[1]{a[tolower($0)];next}{for(i in a)if(!index(tolower($0),i))next}1' <(printf %s\\n "$@") -; }
nisetama
fonte
7

Essa não é uma solução muito boa, mas ilustra um "truque" bem legal

function chained-grep {
    local pattern="$1"
    if [[ -z "$pattern" ]]; then
        cat
        return
    fi    

    shift
    grep -- "$pattern" | chained-grep "$@"
}

cat something | chained-grep all patterns must match order but matter dont
olejorgenb
fonte
1
Use chained-grep()ou function chained-grepnão function chained-grep(): unix.stackexchange.com/questions/73750/…
nisetama 19/01
3

git grep

Aqui está a sintaxe usando a git grepcombinação de vários padrões usando expressões booleanas :

git grep --no-index -e pattern1 --and -e pattern2 --and -e pattern3

O comando acima imprimirá linhas correspondentes a todos os padrões de uma só vez.

--no-index Pesquise arquivos no diretório atual que não é gerenciado pelo Git.

Procure man git-grepajuda.

Veja também:

Para operação OR , consulte:

kenorb
fonte
1

ripgrep

Aqui está o exemplo usando rg:

rg -N '(?P<p1>.*pattern1.*)(?P<p2>.*pattern2.*)(?P<p3>.*pattern3.*)' file.txt

É uma das ferramentas de grepping mais rápidas, pois é construída sobre o mecanismo de expressão regular da Rust, que usa autômatos finitos, SIMD e otimizações literais agressivas para tornar a pesquisa muito rápida.

Consulte também solicitação de recurso relacionado no GH-875 .

kenorb
fonte
1

Aqui está minha opinião, e isso funciona para palavras em várias linhas:

Use find . -type fseguido por tantas
-exec grep -q 'first_word' {} \;
e a última palavra-chave com
-exec grep -l 'nth_word' {} \;

-q
-larquivos de exibição silenciosos / silenciosos

A seguinte lista de nomes de arquivos com as palavras 'rabbit' e 'hole':
find . -type f -exec grep -q 'rabbit' {} \; -exec grep -l 'hole' {} \;

StackRover
fonte
-2

Para encontrar TODAS as palavras (ou padrões), você pode executar o grep no loop FOR . A principal vantagem aqui é pesquisar a partir de uma lista de regexs .

EDITE minha resposta com um exemplo real:

# search_all_regex_and_error_if_missing.sh 

find_list="\
^a+$ \
^b+$ \
^h+$ \
^d+$ \
"

for item in $find_list; do
   if grep -E "$item" file_to_search_within.txt 
   then
       echo "$item found in file."
   else
       echo "Error: $item not found in file. Exiting!"
       exit 1
   fi
done

Agora vamos executá-lo neste arquivo:

hhhhhhhhhh

aaaaaaa

bbbbbbbbb

ababbabaabbaaa

ccccccc

dsfsdf

bbbb

cccdd

aa

caa

# ./search_all_regex_and_error_if_missing.sh

aaaaaaa aa

^ a + $ encontrado no arquivo.

bbbbbbbbb bbbb

^ b + $ encontrado no arquivo.

hhhhhhhhhh

^ h + $ encontrado no arquivo.

Erro: ^ d + $ não encontrado no arquivo. Saindo!

Noam Manos
fonte
1
Sua lógica está com defeito - perguntei pelo ALLoperador, seu código funciona como ORoperador, não AND. E aliás. pois that ( OR) é uma solução muito mais fácil, dada diretamente na pergunta.
greenoldman
@greenoldman A lógica é simples: o for fará um loop em TODAS as palavras / padrões da lista e, se for encontrado no arquivo - o imprimirá. Portanto, basta remover o else se você não precisar de ação, caso a palavra não tenha sido encontrada.
Noam Manos
1
Eu entendo sua lógica e minha pergunta - eu estava perguntando sobre o ANDoperador, o que significa que o arquivo é apenas um resultado positivo se corresponder ao padrão A e o padrão B e padrão C e ... ANDNo seu caso, o arquivo será positivo se corresponder padrão A ou padrão B ou ... Você vê a diferença agora?
greenoldman
@greenoldman não sabe por que você acha que esse loop não verifica E condição para todos os padrões? Então, editei minha resposta com um exemplo real: ele procurará no arquivo todas as expressões regulares da lista e, na primeira que estiver faltando, sairá com erro.
Noam Manos
Você está bem diante dos seus olhos, tem uma correspondência positiva logo após a primeira correspondência ser executada. Você deve "coletar" todos os resultados e computá AND-los. Então você deve reescrever o script para executar em vários arquivos - então talvez você perceba que a pergunta já foi respondida e sua tentativa não traz nada à mesa, desculpe.
greenoldman