Combine duas strings em uma linha com grep


218

Estou tentando usar greppara coincidir com linhas que contêm duas seqüências diferentes. Eu tentei o seguinte, mas isso corresponde a linhas que contêm string1 ou string2 que não é o que eu quero.

grep 'string1\|string2' filename

Então, como faço para corresponder grepapenas às linhas que contêm as duas strings ?


Respostas:


189

Você pode usar grep 'string1' filename | grep 'string2'

Ou, grep 'string1.*string2\|string2.*string1' filename


5
@AlexanderN realmente não posso fazê-lo funcionar com várias linhas, isso é tão estranho que foi aceito .. #
Aquarius Power

1
Não era uma pergunta multilinha. Se fosse multilinha, -P grep suporta regex estilo Perl ...
Scott Prive

20
Só funciona quando 'string1' E 'string2' estão na mesma linha. Se você deseja encontrar linhas com 'string1' ou 'string2', consulte a resposta do usuário45949.
lifeson106

10
a primeira opção: canalizar um grep em um segundo NÃO produz um resultado OR, produz um resultado AND.
Masukomi 23/10/2015

1
Eu useigrep -e "string1" -e "string2"
Ravi Dhoriya ツ

198

Eu acho que é isso que você estava procurando:

grep -E "string1|string2" filename

Eu acho que respostas como esta:

grep 'string1.*string2\|string2.*string1' filename

corresponde apenas ao caso em que ambos estão presentes, não um ou outro ou ambos.


14
não grep -e "string1" -e "string2" filenamefaria o mesmo?
21415 Janosdivenyi

25
isto é como grep para string1 OU string2. a pergunta afirma claramente que eles estão procurando por string1 AND string2.
Orion elenzil

9
Certeza que a questão é muito preciso:How do I match lines that contains *both* strings?
r0estir0bbe

Ele pode imprimir com a mesma linha?
吴毅凡

1
Por que essa resposta ainda está aqui? NÃO é uma resposta para a pergunta.
Prometheus

26

Para procurar arquivos que contenham todas as palavras em qualquer ordem em qualquer lugar:

grep -ril \'action\' | xargs grep -il \'model\' | xargs grep -il \'view_type\'

O primeiro grep inicia uma pesquisa recursiva ( r), ignorando maiúsculas ie minúsculas ( ) e listando (imprimindo) o nome dos arquivos que correspondem ( l) para um termo ( 'action'com aspas simples) ocorrendo em qualquer lugar do arquivo.

Os greps subsequentes pesquisam os outros termos, mantendo a distinção entre maiúsculas e minúsculas e listando os arquivos correspondentes.

A lista final dos arquivos que você receberá será os que contêm esses termos, em qualquer ordem em qualquer lugar do arquivo.


2
Acordado! Vou apenas observar que tive que dar ao xargs um "-d '\ n'" para lidar com nomes de arquivos com espaços. Isso funcionou para mim no Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris

16

Se você tem um grepcom uma -Popção para um limitado perlregex, você pode usar

grep -P '(?=.*string1)(?=.*string2)'

que tem a vantagem de trabalhar com seqüências sobrepostas. É um pouco mais direto usar perlcomo grep, porque você pode especificar a lógica e mais diretamente:

perl -ne 'print if /string1/ && /string2/'

1
Melhor resposta. O shell é muito fácil e rápido, mas quando o padrão se torna complexo, você deve usar Python ou Perl (ou Awk). Não bata com a cabeça contra a parede, tentando provar que isso pode ser feito com casca pura (o que isso significa nos dias de hoje). Um lembrete pessoal, essas ferramentas podem ser usadas na sintaxe de "um forro", que é incorporada em um script de shell existente.
21814 Scott Scott Prive em

12

Seu método era quase bom, faltando apenas o -w

grep -w 'string1\|string2' filename

1
Pelo menos no OS-X e no FreeBSD, ele funciona! Meu palpite é que você está em outra coisa (que o OP não definiu - espero que você não tenha rebaixado a resposta correta para muitos usuários, exceto você).
Leo

Estou no OS-X. Talvez eu não esteja fazendo isso corretamente? Dê uma olhada no que eu fiz: i.imgur.com/PFVlVAG.png
Ariel

1
Ímpar. Eu esperava que a diferença não estivesse no grepping no arquivo, mas, se eu canalizar meu método com o seu ls, obtive o resultado que você não deseja : imgur.com/8eTt3Ak.png - Ambos no OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") e FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Estou curioso para saber qual é o seu grep -V.
Leo

1
Seus exemplos estão funcionando para mim: i.imgur.com/K8LM69O.png Portanto, a diferença é que esse método não capta substrings; eles precisam ser cadeias completas por conta própria. Eu acho que você precisará construir regexps dentro do grep para procurar por substrings. Algo como isto:grep -w 'regexp1\|regexp2' filename
Ariel

2
O OP mostra um exemplo combinando string1 ou string2 e pergunta como combinar linhas que contêm as duas strings. Este exemplo ainda gera OR.
Gustafbstrom #

7

O |operador em uma expressão regular significa or. Ou seja, string1 ou string2 corresponderão. Você poderia fazer:

grep 'string1' filename | grep 'string2'

que canalizará os resultados do primeiro comando para o segundo grep. Isso deve fornecer apenas linhas que correspondam a ambos.


1
Suas declarações são verdadeiras, mas não responder à pergunta OP
Ben Wheeler

Isso responde à pergunta e é assim que a maioria das pessoas a escreve.
Peter K

7

Você pode tentar algo como isto:

(pattern1.*pattern2|pattern2.*pattern1)

4

E como as pessoas sugeriram perl e python, e scripts de shell complicados, aqui uma abordagem simples do awk :

awk '/string1/ && /string2/' filename

Tendo analisado os comentários para a resposta aceita: não, isso não funciona com várias linhas; mas também não foi isso que o autor da pergunta pediu.


3

Não tente usar grep para isso, use awk. Para combinar 2 regexps R1 e R2 no grep, você pensaria que seria:

grep 'R1.*R2|R2.*R1'

enquanto no awk seria:

awk '/R1/ && /R2/'

mas e se se R2sobrepuser a ou é um subconjunto de R1? Esse comando grep simplesmente não funcionaria enquanto o comando awk funcionaria. Digamos que você deseja encontrar linhas que contenham thee heat:

$ echo 'theatre' | grep 'the.*heat|heat.*the'
$ echo 'theatre' | awk '/the/ && /heat/'
theatre

Você precisaria usar 2 greps e um pipe para isso:

$ echo 'theatre' | grep 'the' | grep 'heat'
theatre

e, é claro, se você realmente exigiu que eles fossem separados, você sempre pode escrever no awk o mesmo regexp que você usou no grep e existem soluções alternativas do awk que não envolvem repetir os regexps em todas as seqüências possíveis.

Deixando isso de lado, e se você quisesse estender sua solução para corresponder a três regexps R1, R2 e R3. No grep, essa seria uma dessas más escolhas:

grep 'R1.*R2.*R3|R1.*R3.*R2|R2.*R1.*R3|R2.*R3.*R1|R3.*R1.*R2|R3.*R2.*R1' file
grep R1 file | grep R2 | grep R3

enquanto no awk, seria conciso, óbvio, simples, eficiente:

awk '/R1/ && /R2/ && /R3/'

Agora, e se você realmente quisesse combinar as cadeias literais S1 e S2 em vez das regexps R1 e R2? Você simplesmente não pode fazer isso em uma chamada para grep, você deve escrever um código para escapar de todos os metacarpos do RE antes de chamar grep:

S1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R1')
S2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R2')
grep 'S1.*S2|S2.*S1'

ou use 2 greps e um pipe:

grep -F 'S1' file | grep -F 'S2'

que novamente são más escolhas, enquanto que com o awk você simplesmente usa um operador de string em vez do operador regexp:

awk 'index($0,S1) && index($0.S2)'

Agora, e se você quisesse corresponder 2 regexps em um parágrafo em vez de uma linha? Não pode ser feito no grep, trivial no awk:

awk -v RS='' '/R1/ && /R2/'

Que tal um arquivo inteiro? Novamente, não pode ser feito em grep e trivial em awk (desta vez, estou usando o GNU awk para multi-char RS por concisão, mas não há muito mais código em nenhum awk ou você pode escolher um char de controle que você sabe que não estar na entrada para o RS fazer o mesmo):

awk -v RS='^$' '/R1/ && /R2/'

Portanto, se você deseja encontrar vários regexps ou strings em uma linha ou parágrafo ou arquivo, não use grep, use awk.


Não awk '/R1/ && /R2/'diferencia maiúsculas de minúsculas?
Prometheus

@Hashim - não. Para torná-lo case-insensitive com GNU awk você faria awk -v IGNORECASE=1 '/R1/ && /R2/'e com qualquer awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton

3
grep string1\|string2 FILENAME 

GNU grep versão 3.1


2

Linhas encontradas que começam apenas com 6 espaços e terminam com:

 cat my_file.txt | grep
 -e '^      .*(\.c$|\.cpp$|\.h$|\.log$|\.out$)' # .c or .cpp or .h or .log or .out
 -e '^      .*[0-9]\{5,9\}$' # numers between 5 and 9 digist
 > nolog.txt

2

Digamos que precisamos encontrar a contagem de várias palavras em um arquivo de teste de arquivo. Existem duas maneiras de fazer isso

1) Use o comando grep com padrão de correspondência regex

grep -c '\<\(DOG\|CAT\)\>' testfile

2) Use o comando egrep

egrep -c 'DOG|CAT' testfile 

Com o egrep, você não precisa se preocupar com a expressão e apenas separar as palavras por um separador de tubulação.


2

git grep

Aqui está a sintaxe usando git grepvários padrões:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

Você também pode combinar padrões com expressões booleanas como --and, --ore --not.

Procure man git-grepajuda.


--all-matchAo fornecer várias expressões padrão, esse sinalizador é especificado para limitar a correspondência aos arquivos que possuem linhas para corresponder a todas elas .

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

-l/ --files-with-matches/ --name-onlyMostrar apenas os nomes dos arquivos.

-eO próximo parâmetro é o padrão. O padrão é usar o regexp básico.

Outros parâmetros a considerar:

--threads Número de threads de trabalho grep a serem usados.

-q/ --quiet/ --silentDo not saída combinado linhas; saia com o status 0 quando houver uma correspondência.

Para alterar o tipo de padrão, você também pode usar -G/ --basic-regexp(default), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f filee outros.

Palavras-chave:

Para operação OR , consulte:


2
Sempre pensei que "git grep" só pode ser executado dentro de um repositório git. Eu não estava ciente da opção --no-index. Obrigado por apontar isso!
Kamaraju Kusumanchi 01/07/19

1

Coloque as strings que você deseja grep em um arquivo

echo who    > find.txt
echo Roger >> find.txt
echo [44][0-9]{9,} >> find.txt

Em seguida, pesquise usando -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

1
grep '(string1.*string2 | string2.*string1)' filename

vai ficar alinhado com string1 e string2 em qualquer ordem


De que maneira isso é diferente das pelo menos as duas principais respostas?
Luk2302

1
grep -i -w 'string1\|string2' filename

Isso funciona para correspondência exata de palavras e palavras sem distinção entre maiúsculas e minúsculas, pois -i é usado


0

para correspondência de várias linhas:

echo -e "test1\ntest2\ntest3" |tr -d '\n' |grep "test1.*test3"

ou

echo -e "test1\ntest5\ntest3" >tst.txt
cat tst.txt |tr -d '\n' |grep "test1.*test3\|test3.*test1"

só precisamos remover o caractere de nova linha e ele funciona!


0

Você deveria ter grepassim:

$ grep 'string1' file | grep 'string2'

1
Isso executa um AND lógico. O OP quer um OR lógico.
Ben Wheeler

1
@ BenWheeler: Da pergunta: "Então, como faço para combinar com grep apenas as linhas que contêm as duas strings?"
que você

0

Geralmente encontro o mesmo problema que o seu e acabei de escrever um pedaço de script:

function m() { # m means 'multi pattern grep'

    function _usage() {
    echo "usage: COMMAND [-inH] -p<pattern1> -p<pattern2> <filename>"
    echo "-i : ignore case"
    echo "-n : show line number"
    echo "-H : show filename"
    echo "-h : show header"
    echo "-p : specify pattern"
    }

    declare -a patterns
    # it is important to declare OPTIND as local
    local ignorecase_flag  filename linum header_flag colon result OPTIND

    while getopts "iHhnp:" opt; do
    case $opt in
        i)
        ignorecase_flag=true ;;
        H)
        filename="FILENAME," ;;
        n)
        linum="NR," ;;
        p)
        patterns+=( "$OPTARG" ) ;;
        h)
        header_flag=true ;;
        \?)
        _usage
        return ;;
    esac
    done

    if [[ -n $filename || -n $linum ]]; then
    colon="\":\","
    fi

    shift $(( $OPTIND - 1 ))

    if [[ $ignorecase_flag == true ]]; then
    for s in "${patterns[@]}"; do
            result+=" && s~/${s,,}/"
    done
    result=${result# && }
    result="{s=tolower(\$0)} $result"
    else
    for s in "${patterns[@]}"; do
            result="$result && /$s/"
    done
    result=${result# && }
    fi

    result+=" { print "$filename$linum$colon"\$0 }"

    if [[ ! -t 0 ]]; then       # pipe case
    cat - | awk "${result}"
    else
    for f in "$@"; do
        [[ $header_flag == true ]] && echo "########## $f ##########"
        awk "${result}" $f
    done
    fi
}

Uso:

echo "a b c" | m -p A 
echo "a b c" | m -i -p A # a b c

Você pode colocá-lo em .bashrc, se quiser.


0

Quando as duas strings estiverem em sequência, coloque um padrão no grepcomando:

$ grep -E "string1(?.*)string2" file

Exemplo se as seguintes linhas estiverem contidas em um arquivo chamado Dockerfile:

FROM python:3.8 as build-python
FROM python:3.8-slim

Para obter a linha que contém as seqüências de caracteres: FROM pythone as build-pythondepois use:

$ grep -E "FROM python:(?.*) as build-python" Dockerfile

A saída mostrará apenas a linha que contém as duas strings :

FROM python:3.8 as build-python

-2

ripgrep

Aqui está o exemplo usando rg:

rg -N '(?P<p1>.*string1.*)(?P<p2>.*string2.*)' file.txt

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

Use-o, especialmente quando você estiver trabalhando com dados grandes.

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


1
Esta resposta não está certa. Os grupos de captura nomeados são desnecessários e isso não lida com o caso quando string2aparece antes string1. A solução mais simples para esse problema é rg string1 file.txt | rg string2.
BurntSushi5
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.