Como usar o grep para procurar uma linha com uma das duas palavras, mas não ambas?

11

Eu quero procurar linhas com 'word1' XOR 'word2' em um arquivo de texto. Portanto, deve exibir linhas com a palavra 1, palavra 2, mas não as linhas com ambas as palavras. Eu queria usar o XOR, mas não sei como escrever isso na linha de comando do Linux.

Eu tentei:

grep 'word1\|word2' text.txt
grep word1 word2 text.txt
grep word1 text.txt | grep word2
grep 'word1\^word2' text.txt

e muito mais, mas não conseguiu sucesso.

Lukali
fonte

Respostas:

6

grep 'word1\|word2' text.txtprocura por linhas contendo word1ou word2. Isso inclui linhas que contêm ambos.

grep word1 text.txt | grep word2procura por linhas contendo word1e word2. As duas palavras podem se sobrepor (por exemplo, foobarcontém fooe ob). Outra maneira de procurar linhas contendo as duas palavras, mas apenas de maneira não sobreposta, é procurá-las em qualquer ordem:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2procura por linhas contendo word1mas não word2. A -vopção diz ao grep para manter as linhas não correspondentes e remover as linhas correspondentes, em vez do oposto. Isso fornece metade dos resultados desejados. Ao adicionar a pesquisa simétrica, você obtém todas as linhas que contêm exatamente uma das palavras.

grep word1 text.txt | grep -v word2
grep word2 text.txt | grep -v word1

Como alternativa, você pode começar pelas linhas que contêm uma das palavras e remover as linhas que contêm as duas palavras. Dado os blocos de construção acima, isso é fácil se as palavras não se sobrepuserem.

grep 'word1\|word2' text.txt | grep -v 'word1.*word2\|word2.*word1'
Gilles 'SO- parar de ser mau'
fonte
Obrigado, isto é exatamente o que eu estava procurando. As outras respostas também são muito interessantes, então, mal olhe para elas. Obrigado a todos por contribuir.
Lukali
17

Com o GNU awk:

$ printf '%s\n' {foo,bar}{bar,foo} neither | gawk 'xor(/foo/,/bar/)'
foofoo
barbar

Ou portably:

awk '((/foo/) + (/bar/)) % 2'

Com um grepsuporte para -P(PCRE):

grep -P '^((?=.*foo)(?!.*bar)|(?=.*bar)(?!.*foo))'

Com sed:

sed '
  /foo/{
    /bar/d
    b
  }
  /bar/!d'

Se você considerar apenas palavras inteiras (que não é nem foonem barem foobarou barbarpor exemplo), você precisa decidir como essas palavras são delimitados. Se for por qualquer caractere que não seja letras, dígitos e sublinhado, como a -wopção de muitas grepimplementações, altere-os para:

gawk 'xor(/\<foo\>/,/\<bar\>/)'
awk '((/(^|[^[:alnum:]_)foo([^[:alnum:]_]|$)/) + \
      (/(^|[^[:alnum:]_)bar([^[:alnum:]_]|$)/)) % 2'
grep -P '^((?=.*\bfoo\b)(?!.*\bbar\b)|(?=.*\bbar\b)(?!.*\bfoo\b))'

Por sedque se torna um pouco complicado se não tiver uma sedaplicação como o GNU sed que suporta \</ \>como limites da palavra como o GNU awkfaz.

Stéphane Chazelas
fonte
6
Stephane, por favor escreva um livro sobre scripts de shell!
pfnuesel
Desculpe, só iniciei a linha de comando há algumas semanas. Como eu o forçaria a procurar apenas palavras? Tentei -Pw e -wP, mas isso me deu a saída errada. Também tentei usar '' entre * word1 / * word2 e em torno de word1 / word2.
Lukali
@Lukali, veja editar.
Stéphane Chazelas
2

Uma solução bash:

#!/bin/bash 
while (( $# )); do
    a=0 ; [[ $1 =~ foo ]] && a=1 
    b=0 ; [[ $1 =~ bar ]] && b=1
    (( a ^ b )) && echo "$1"
    shift
done

Para testá-lo:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
NotAnUnixNazi
fonte