Respostas:
Uma ferramenta que não grep
é o caminho a percorrer.
Usando perl, por exemplo, o comando seria:
perl -ne 'print if /pattern1/ xor /pattern2/'
perl -ne
executa o comando fornecido sobre cada linha de stdin, que nesse caso imprime a linha se corresponder /pattern1/ xor /pattern2/
ou, em outras palavras, corresponde a um padrão, mas não ao outro (exclusivo ou).
Isso funciona para o padrão em qualquer ordem e deve ter um desempenho melhor do que várias invocações grep
e também é menos digitado.
Ou, ainda mais curto, com awk:
awk 'xor(/pattern1/,/pattern2/)'
ou para versões do awk que não possuem xor
:
awk '/pattern1/+/pattern2/==1`
xor
disponível apenas no GNU Awk?
/pattern1/+/pattern2/==1
ir xor
está ausente.
\b
) nos próprios padrões, ie \bword\b
.
Tente com egrep
egrep 'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'
grep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
Direct invocation as either egrep or fgrep is deprecated
- prefiragrep -E
-f
e -e
opções, embora seja antigo egrep
e fgrep
continuará sendo suportado por um tempo.
grep
(ou suportes -F
, -E
, -e
, -f
como exige POSIX) é em /usr/xpg4/bin
. Os utilitários /bin
são antiquados.
Com grep
implementações que suportam expressões regulares do tipo perl (como pcregrep
ou GNU ou ast-open grep -P
), você pode fazer isso em uma grep
chamada com:
grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'
Ou seja, encontrar as linhas que correspondem, pat1
mas não pat2
, ou pat2
mas não pat1
.
(?=...)
e, (?!...)
respectivamente, são operadores de antecipação e negativo. Portanto, tecnicamente, o item acima procura o início do assunto ( ^
), desde que seja seguido .*pat1
e não seguido por .*pat2
, ou o mesmo com pat1
e pat2
invertido.
Isso é subótimo para linhas que contêm os dois padrões, pois seriam procurados duas vezes. Você poderia usar operadores perl mais avançados, como:
grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'
(?(1)yespattern|nopattern)
corresponde contra yespattern
se o grupo de captura 1
st (vazio ()
acima) corresponder, ou nopattern
não. Se esse ()
partidas, isso significa que pat1
não se encontraram, então olhamos para pat2
(olhar positivo à frente), e nós olhamos para não pat2
o contrário (em frente olhar negativo).
Com sed
, você pode escrever:
sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'
grep: the -P option only supports a single pattern
, pelo menos em todos os sistemas aos quais tenho acesso. +1 para sua segunda solução, no entanto.
grep
. pcregrep
e o ast-open grep não tem esse problema. Substituí o múltiplo -e
pelo operador RE alternativo, portanto, ele deve funcionar com o GNU grep
também agora.
Em termos booleanos, você está procurando A xor B, que pode ser escrito como
(A e não B)
ou
(B e não A)
Dado que sua pergunta não menciona que você está preocupado com a ordem da saída, desde que as linhas correspondentes sejam mostradas, a expansão booleana de A xor B é bastante simples no grep:
$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c
sort | uniq
.
Para o seguinte exemplo:
# Patterns:
# apple
# pear
# Example line
line="a_apple_apple_pear_a"
Isso pode ser feito puramente com grep -E
, uniq
e wc
.
# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)
Se grep
for compilado com expressões regulares do Perl, você poderá corresponder na última ocorrência, em vez de precisar canalizar para uniq
:
# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)
Saída do resultado:
# Only one of the words exists if the result is < 2
((result > 0)) &&
if (($result < 2)); then
echo Only one word matched
else
echo Both words matched
fi
Uma linha:
(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched
Se você não deseja codificar o padrão, montá-lo com um conjunto variável de elementos pode ser automatizado com uma função.
Isso também pode ser feito de forma nativa no Bash como uma função sem canais ou processos adicionais, mas estaria mais envolvido e provavelmente está fora do escopo da sua pergunta.
Big apple\n
e pear-shaped\n
, a saída deverá conter as duas linhas. Sua solução receberá uma contagem de 2; a versão longa informaria "Ambas as palavras correspondiam" (que é uma resposta à pergunta errada) e a versão curta não dizia nada. (3) Uma sugestão: usar -o
aqui é uma péssima idéia, porque oculta as linhas que contêm as correspondências, portanto você não pode ver quando as duas palavras aparecem na mesma linha. … (Continua)
uniq
/ sort -u
e a expressão regular sofisticada de Perl para corresponder apenas à última ocorrência em cada linha não são realmente uma resposta útil para esta pergunta. Mas, mesmo se o fizessem, ainda seria uma resposta ruim, porque você não explica como eles contribuem para responder à pergunta. (Veja a resposta de Stéphane Chazelas para um exemplo de uma boa explicação.)
[a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+
? (2) E se uma das palavras / padrões aparecer mais de uma vez em uma linha (e a outra não aparecer)? Isso é equivalente à palavra que aparece uma vez ou conta como várias ocorrências?