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

10

Eu quero procurar linhas com 'word1' XOR 'word2' em um arquivo de texto. Por isso, deve sair linhas com word1, word2, 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.

    
por Lukali 06.02.2018 / 18:44

3 respostas

6

grep 'word1\|word2' text.txt procura por linhas que contenham word1 ou word2 . Isso inclui linhas que contenham ambos.

grep word1 text.txt | grep word2 procura por linhas contendo word1 e word2 . As duas palavras podem se sobrepor (por exemplo, foobar contém foo e ob ). Outra maneira de procurar linhas contendo ambas as 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 word2 procura por linhas contendo word1 , mas não word2 . A opção -v diz ao grep para manter linhas não correspondentes e remover linhas correspondentes, em vez do oposto. Isso lhe dá metade dos resultados que você queria. Ao adicionar a pesquisa simétrica, você obtém todas as linhas contendo 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. Dados 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'
    
por 06.02.2018 / 22:23
17

Com o GNU awk :

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

Ou portavelmente:

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

Com um grep com suporte para -P (PCRE):

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

com sed :

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

Se você quiser considerar somente palavras inteiras (que não há foo nem bar em foobar ou barbar , por exemplo), você precisa decidir como essas palavras são delimitadas. Se for por qualquer caractere diferente de letras, dígitos e sublinhado como a opção -w de muitas grep implementações, altere-as para:

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

Para sed que se torna um pouco complicado, a menos que você tenha uma implementação de sed como o GNU sed que suporta \< / \> como limites de palavras como GNU awk .

    
por 06.02.2018 / 18:48
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 testar:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
    
por 06.02.2018 / 23:08