Como grep para vários padrões com padrão com um caractere de barra vertical?

623

Quero encontrar todas as linhas em vários arquivos que correspondam a um dos dois padrões. Tentei encontrar os padrões que estou procurando digitando

grep (foo|bar) *.txt

mas o shell interpreta o |como um pipe e reclama quando barnão é um executável.

Como posso grep para vários padrões no mesmo conjunto de arquivos?

Dan
fonte
possível duplicação do Grep: como adicionar uma condição "OU"?
phuclv
grep 'word1 \ | word2 \ | word3' / caminho / para / arquivo
lambodar 20/09

Respostas:

860

Primeiro, você precisa proteger o padrão da expansão pelo shell. A maneira mais fácil de fazer isso é colocar aspas simples. Aspas simples impedem a expansão de qualquer coisa entre elas (incluindo barras invertidas); a única coisa que você não pode fazer é ter aspas simples no padrão.

grep 'foo*' *.txt

Se você precisar de uma única citação, poderá escrevê-la como '\''(final da string literal, literal literal, open string literal).

grep 'foo*'\''bar' *.txt

Segundo, o grep suporta duas sintaxes para padrões. A sintaxe padrão antiga ( expressões regulares básicas ) não suporta o |operador alternation ( ), embora algumas versões a tenham como uma extensão, mas gravadas com uma barra invertida.

grep 'foo\|bar' *.txt

A maneira portátil é usar a sintaxe mais recente, expressões regulares estendidas . Você precisa passar a -Eopção greppara selecioná-lo. No Linux, você também pode digitar em egrepvez de grep -E(em outras unidades, você pode transformar isso em um alias).

grep -E 'foo|bar' *.txt

Outra possibilidade quando você está apenas procurando por um dos vários padrões (em vez de criar um padrão complexo usando disjunção) é passar vários padrões para grep. Você pode fazer isso precedendo cada padrão com a -eopção

grep -e foo -e bar *.txt
Gilles
fonte
18
Como nota de rodapé - quando os padrões são fixos, você realmente deve adquirir o hábito de , fgrepou grep -F, para pequenos padrões, a diferença será insignificante, mas à medida que eles se prolongam, os benefícios começam a aparecer ...
TC1
7
@ TC1 O fgrep está obsoleto de acordo com a página de
manual
18
@ TC1 Se grep -Fum benefício real de desempenho depende da implementação grep: alguns deles aplicam o mesmo algoritmo de qualquer maneira, de modo que -Ffaz diferença apenas no tempo gasto na análise do padrão e não na pesquisa de tempo. O GNU grep não é mais rápido -F, por exemplo (ele também possui um bug que torna grep -Fmais lento os códigos de idioma multibyte - o mesmo padrão constante com ele grepé significativamente mais rápido!). Por outro lado, o BusyBox grep se beneficia muito -Fcom arquivos grandes.
Gilles
4
Talvez deva ser mencionado que, para padrões mais complicados, nos quais a alternância deve ser apenas para uma parte da expressão regular, ela pode ser agrupada com "\ (" e "\)" (o escape é para as "expressões regulares básicas" padrão) ) (?).
Peter Mortensen
4
Observe que egrepé anterior grep -E. Não é específico do GNU (certamente não tem nada a ver com Linux). Na verdade, você ainda encontrará sistemas como o Solaris nos quais o padrão grepainda não é compatível -E.
Stéphane Chazelas
90
egrep "foo|bar" *.txt

ou

grep "foo\|bar" *.txt
grep -E "foo|bar" *.txt

citando seletivamente a página de manual do gnu-grep:

   -E, --extended-regexp
          Interpret PATTERN as an extended regular expression (ERE, see below).  (-E is specified by POSIX.)

Matching Control
   -e PATTERN, --regexp=PATTERN
          Use PATTERN as the pattern.  This can be used to specify multiple search patterns, or to protect  a  pattern
          beginning with a hyphen (-).  (-e is specified by POSIX.)

(...)

   grep understands two different versions of regular expression syntax: basic and extended.”  In  GNU grep,  there
   is  no  difference  in  available  functionality  using  either  syntax.   In  other implementations, basic regular
   expressions are less powerful.  The following description applies to extended regular expressions; differences  for
   basic regular expressions are summarized afterwards.

No começo, não li mais, então não reconheci as diferenças sutis:

Basic vs Extended Regular Expressions
   In basic regular expressions the meta-characters ?, +, {, |, (, and ) lose their special meaning; instead  use  the
   backslashed versions \?, \+, \{, \|, \(, and \).

Sempre usei egrep e desnecessariamente parens, porque aprendi com exemplos. Agora eu aprendi algo novo. :)

Usuário desconhecido
fonte
22

Como o TC1 disse, -Fparece ser uma opção utilizável:

$> cat text
some text
foo
another text
bar
end of file

$> patterns="foo
bar" 

$> grep -F "${patterns}" text
foo
bar
ДМИТРИЙ МАЛИКОВ
fonte
1
@poige Eu não sabia sobre a opção $ 'foo \ nbar', não tenho certeza de como a expansão funciona aqui, precisa procurar, mas obrigado, isso é realmente útil.
haridsv
Agradável! Essa opção também parece torná-la muito mais rápida (pois desativa o regex).
Qwertzguy
15

Primeiro, você precisa usar aspas para caracteres especiais. Segundo, mesmo assim, grepnão entenderá a alternância diretamente; você precisaria usar egrep, ou ( grepapenas com GNU ) grep -E.

egrep 'foo|bar' *.txt

(Os parênteses são desnecessários, a menos que a alternância faça parte de uma regex maior.)

geekosaur
fonte
4
Na verdade, grep -Eé mais padrão do que egrep.
Jw013
8

Se você não precisa de expressões regulares, é muito mais rápido usar fgrepou grep -Fcom vários parâmetros -e, como este:

fgrep -efoo -ebar *.txt

fgrep(alternativamente grep -F) é muito mais rápido que o grep normal, porque procura por seqüências fixas em vez de expressões regulares.

Moustafa Elqabbany
fonte
4
Por favor, veja também os comentários nesta página mencionando que fgrepestá obsoleto.
Php
6

Você pode tentar o comando abaixo para obter o resultado:

egrep 'rose.*lotus|lotus.*rose' some_file
Abhishek
fonte
3

Uma maneira barata e alegre de grep para vários padrões:

$ echo "foo" > ewq ; echo "bar" >> ewq ; grep -H -f ewq *.txt ; rm ewq
DHDHDHD
fonte
Poderia se beneficiar de uma explicação.
Peter Mortensen
2
A explicação é que a -fopção grep pega um arquivo com vários padrões. Em vez de criar um arquivo temporário (que você pode esquecer de apagar depois), basta usar a substituição do processo do shell:grep -f <(echo foo; echo bar) *.txt
Jakob
3

Pipe ( |) é um caractere de shell especial, portanto, ele precisa ser escapado ( \|) ou citado de acordo com o manual ( man bash):

A citação é usada para remover o significado especial de certos caracteres ou palavras do shell. Ele pode ser usado para desativar o tratamento especial de caracteres especiais, impedir que palavras reservadas sejam reconhecidas como tal e impedir a expansão de parâmetros.

A inclusão de caracteres entre aspas duplas preserva o valor literal de todos os caracteres entre aspas

Uma barra invertida não citada ( \) é o caractere de escape.

Veja: Quais caracteres precisam ser escapados no Bash?

Aqui estão alguns exemplos (usando ferramentas ainda não mencionadas):

  • Usando ripgrep:

    • rg "foo|bar" *.txt
    • rg -e foo -e bar *.txt
  • Usando git grep:

    • git grep --no-index -e foo --or -e bar

      Nota: Ele também suporta expressões booleanas como --and, --ore --not.

Para operação AND por linha, consulte: Como executar o grep com vários padrões AND?

Para operação AND por arquivo, consulte: Como verificar se existem várias seqüências de caracteres ou expressões regulares em um arquivo?

kenorb
fonte
3

Eu tinha logs de acesso em que as datas eram estupidamente formatadas: [30 / jun / 2013: 08: 00: 45 +0200]

Mas eu precisava exibi-lo como: 30 / jun / 2013 08:00:45

O problema é que, usando "OR" na minha declaração grep, eu estava recebendo as duas expressões de correspondência em duas linhas separadas.

Aqui está a solução:

grep -in myURL_of_interest  *access.log  | \
grep -Eo '(\b[[:digit:]]{2}/[[:upper:]][[:lower:]]{2}/[[:digit:]]{4}|[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\b)'   \
| paste - - -d" " > MyAccess.log
tsmets
fonte
2

TL; DR: se você quiser fazer mais coisas depois de corresponder a um dos vários padrões, coloque-os como em \(pattern1\|pattern2\)

exemplo: eu quero encontrar todos os lugares onde uma variável que contém o nome 'date' é definida como uma String ou int. (por exemplo, "int cronDate =" ou "String textFormattedDateStamp ="):

cat myfile | grep '\(int\|String\) [a-zA-Z_]*date[a-zA-Z_]* =' 

Com grep -E, você não precisa escapar dos parênteses ou do tubo, ou seja,grep -E '(int|String) [a-zA-Z_]*date[a-zA-Z_]* ='

jeremysprofile
fonte
1

Isso funciona para mim

root@gateway:/home/sshuser# aws ec2 describe-instances --instance-ids i-2db0459d |grep 'STATE\|TAG'

**STATE**   80      stopped

**STATE**REASON     Client.UserInitiatedShutdown    Client.UserInitiatedShutdown: User initiated shutdown

**TAGS**    Name    Magento-Testing root@gateway:/home/sshuser#
Mansur Ali
fonte
1

Existem várias maneiras de fazer isso.

  1. grep 'foo\|bar' *.txt
  2. egrep 'foo|bar' *.txt
  3. find . -maxdepth 1 -type f -name "*.txt" | xargs grep 'foo\|bar'
  4. find . -maxdepth 1 -type f -name "*.txt" | xargs egrep 'foo|bar'

A 3ª e a 4ª opção cumprem apenas nos arquivos e evitam que os diretórios tenham .txtseus nomes.
Portanto, conforme seu caso de uso, você pode usar qualquer uma das opções mencionadas acima.
Obrigado!!

Bhagyesh Dudhediya
fonte
0

para adicionar à resposta do @ geekosaur , se você tiver vários padrões que também contenham guias e espaço, use o seguinte comando

grep -E "foo[[:blank:]]|bar[[:blank:]]"

onde [[:blank:]]é a classe de caracteres RE que representa um espaço ou um caractere de tabulação

Fuseteam
fonte