Como usar o sed / grep para extrair texto entre duas palavras?


134

Estou tentando gerar uma string que contém tudo entre duas palavras de uma string:

entrada:

"Here is a String"

resultado:

"is a"

Usando:

sed -n '/Here/,/String/p'

inclui os pontos de extremidade, mas não quero incluí-los.


8
Qual deve ser o resultado se a entrada for Here is a Here String? Ou I Hereby Dub Thee Sir Stringy?
ghoti

5
PARA SUA INFORMAÇÃO. Seu comando significa imprimir tudo entre a linha que possui a palavra Here e a linha que possui a palavra String - e não o que você deseja.
Hai Vu #

A outra sedFAQ comum é "como posso extrair texto entre linhas específicas"; isto é stackoverflow.com/questions/16643288/…
tripleee

Respostas:


109
sed -e 's/Here\(.*\)String/\1/'

2
Obrigado! E se eu quisesse encontrar tudo entre "one is" e "String" em "Here is one is a String"? (sed -e 's / um é (*) Corda / \ 1 /.'?
user1190650

5
@ user1190650 Isso funcionaria se você também quiser ver o "Aqui está um". Você pode testá-lo: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. Se você quer apenas a parte entre "é" e "String", então você precisa fazer a regex corresponder toda a linha: sed -e 's/.*one is\(.*\)String.*/\1/'. Em sed, s/pattern/replacement/diga "substitua 'substituição' por 'padrão' em cada linha". Ele mudará apenas qualquer coisa que corresponda a "padrão"; portanto, se você quiser substituir a linha inteira, precisará fazer "padrão" corresponder à linha inteira.
Brian Campbell

9
Isso interrompe quando a entrada éHere is a String Here is a String
Jay D

1
Seria ótimo ver a solução para um caso: "Aqui está uma string blá blá Aqui está 1 uma string blá blá Aqui está 2 uma string blá blá blá" A saída deve pegar apenas a primeira substring entre Here e String "
Jay D

1
O @JayD sed não suporta correspondência não gananciosa; consulte esta pergunta para algumas alternativas recomendadas.
Brian Campbell

179

O GNU grep também pode suportar um olhar positivo e negativo positivo: para o seu caso, o comando seria:

echo "Here is a string" | grep -o -P '(?<=Here).*(?=string)'

Se houver várias ocorrências de Heree string, você poderá escolher se deseja corresponder da primeira Heree da última stringou individualmente. Em termos de regex, é chamado de correspondência gananciosa (primeiro caso) ou correspondência não gananciosa (segundo caso)

$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*(?=string)' # Greedy match
 is a string, and Here is another 
$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*?(?=string)' # Non-greedy match (Notice the '?' after '*' in .*)
 is a 
 is another 

31
Observe que a -Popção do GNU grep não existe no grepincluído no * BSD ou nos que vêm com qualquer SVR4 (Solaris, etc). No FreeBSD, você pode instalar a devel/pcreporta que inclui pcregrep, que suporta o PCRE (e olha para frente / atrás). As versões anteriores do OSX usavam o GNU grep, mas no OSX Mavericks, -Pé derivado da versão do FreeBSD, que não inclui a opção.
ghoti

1
Olá, Como extraio apenas conteúdo distinto?
Durgesh Suthar

4
Isso não funciona porque se a sequência final "string" ocorrer mais de uma vez, ela receberá a última ocorrência, não a próxima ocorrência.
Buttle Butkus

6
No caso de Here is a string a string, ambas " is a " e " is a string a "são respostas válidas (ignore as aspas), conforme os requisitos da pergunta. Depende de você qual deles você deseja e a resposta pode ser diferente de acordo. De qualquer forma, para sua exigência, isso funcionará:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane 27/10

2
@BND, você precisa habilitar o recurso de pesquisa em várias linhas do pcregrep . echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
anishsane

58

A resposta aceita não remove o texto que poderia ser antes Hereou depois String. Isso vai:

sed -e 's/.*Here\(.*\)String.*/\1/'

A principal diferença é a adição de .*imediatamente antes Heree depois String.


Sua resposta é promissora. Uma questão embora. Como posso extraí-lo para a primeira String vista, se houver várias String na mesma linha? Obrigado
Mian Asbat Ahmad

@MianAsbatAhmad Você gostaria de tornar o *quantificador entre Heree Stringnão ganancioso (ou preguiçoso). No entanto, o tipo de regex usado pelo sed não suporta quantificadores preguiçosos ( ?imediatamente após .*) de acordo com esta pergunta do Stackoverflow. Geralmente, para implementar um quantificador preguiçoso, você apenas compara tudo, exceto o token que não deseja, mas nesse caso, não há apenas um único token, mas uma string inteira String.
veículo com rodas

Obrigado, eu recebi a resposta usando o awk, stackoverflow.com/questions/51041463/… #
4800 Mian Asbat Ahmad

Infelizmente isso não funciona se a cadeia tem quebras de linha
Witalo Benicio

Não deveria. .não corresponde a quebras de linha. Se você quiser combinar quebras de linha, poderá substituí-lo .por algo como [\s\s].
veículo com rodas

35

Você pode retirar as strings apenas no Bash :

$ foo="Here is a String"
$ foo=${foo##*Here }
$ echo "$foo"
is a String
$ foo=${foo%% String*}
$ echo "$foo"
is a
$

E se você tem um GNU grep que inclui PCRE , pode usar uma asserção de largura zero:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a

por que esse método é tão lento? ao retirar uma página html grande usando esse método, leva 10 segundos.
Adam Johns

@AdamJohns, qual método? O PCRE? O PCRE é bastante complexo de analisar, mas 10 segundos parecem extremos. Se você estiver preocupado, recomendo que você faça uma pergunta, incluindo código de exemplo, e veja o que os especialistas dizem.
ghoti

Eu acho que foi muito lento para mim porque estava segurando uma fonte de arquivo html muito grande em uma variável. Quando escrevi o conteúdo do arquivo e o analisei, a velocidade aumentou drasticamente.
Adam Johns

22

Através do GNU awk,

$ echo "Here is a string" | awk -v FS="(Here|string)" '{print $2}'
 is a 

O grep com -P( perl-regexp ) suporta os parâmetros \K, o que ajuda a descartar os caracteres correspondidos anteriormente. No nosso caso, a string correspondida anteriormente foi Heredescartada da saída final.

$ echo "Here is a string" | grep -oP 'Here\K.*(?=string)'
 is a 
$ echo "Here is a string" | grep -oP 'Here\K(?:(?!string).)*'
 is a 

Se você deseja que a saída seja is a, tente o seguinte,

$ echo "Here is a string" | grep -oP 'Here\s*\K.*(?=\s+string)'
is a
$ echo "Here is a string" | grep -oP 'Here\s*\K(?:(?!\s+string).)*'
is a

Isso não funciona para :, echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}'ele retorna apenas em is avez de deveria ser is a is a@Avinash Raj
alper

20

Se você possui um arquivo longo com muitas ocorrências de várias linhas, é útil imprimir primeiro as linhas numéricas:

cat -n file | sed -n '/Here/,/String/p'

3
Obrigado! Esta é a única solução que funcionou no meu caso (arquivo de texto com várias linhas, em vez de uma única sequência sem quebras de linha). Obviamente, para tê-lo sem numeração de linha, a -nopção in catdeve ser omitida.
Jeffrey Lebowski

... nesse caso, catpode ser totalmente omitido; sedsabe ler um arquivo ou entrada padrão.
tripleee

9

Isso pode funcionar para você (GNU sed):

sed '/Here/!d;s//&\n/;s/.*\n//;:a;/String/bb;$!{n;ba};:b;s//\n&/;P;D' file 

Isso apresenta cada representação do texto entre dois marcadores (nesta instância Heree String) em uma nova linha e preserva as novas linhas dentro do texto.


7

Todas as soluções acima apresentam deficiências onde a última sequência de pesquisa é repetida em outro local da sequência. Eu achei melhor escrever uma função bash.

    function str_str {
      local str
      str="${1#*${2}}"
      str="${str%%$3*}"
      echo -n "$str"
    }

    # test it ...
    mystr="this is a string"
    str_str "$mystr" "this " " string"

6

Você pode usar dois comandos s

$ echo "Here is a String" | sed 's/.*Here//; s/String.*//'
 is a 

Também funciona

$ echo "Here is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a

$ echo "Here is a StringHere is a StringHere is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a 

6

Para entender o sedcomando, precisamos construí-lo passo a passo.

Aqui está o seu texto original

user@linux:~$ echo "Here is a String"
Here is a String
user@linux:~$ 

Vamos tentar remover a Herestring com a sopção ubstition emsed

user@linux:~$ echo "Here is a String" | sed 's/Here //'
is a String
user@linux:~$ 

Neste ponto, acredito que você seria capaz de remover Stringtambém

user@linux:~$ echo "Here is a String" | sed 's/String//'
Here is a
user@linux:~$ 

Mas este não é o resultado desejado.

Para combinar dois comandos sed, use a -eopção

user@linux:~$ echo "Here is a String" | sed -e 's/Here //' -e 's/String//'
is a
user@linux:~$ 

Espero que isto ajude



1

Problema. Minhas mensagens de correio de garras armazenadas são agrupadas da seguinte maneira e estou tentando extrair as linhas de assunto:

Subject: [SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular
 link in major cell growth pathway: Findings point to new potential
 therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is
 Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as
 a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway
 identified [Lysosomal amino acid transporter SLC38A9 signals arginine
 sufficiency to mTORC1]]
Message-ID: <20171019190902.18741771@VictoriasJourney.com>

Por A2 neste tópico, como usar o sed / grep para extrair texto entre duas palavras? a primeira expressão, abaixo, "funciona", desde que o texto correspondente não contenha uma nova linha:

grep -o -P '(?<=Subject: ).*(?=molecular)' corpus/01

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

No entanto, apesar de tentar várias variantes ( .+?; /s; ...), não consegui fazê-las funcionar:

grep -o -P '(?<=Subject: ).*(?=link)' corpus/01
grep -o -P '(?<=Subject: ).*(?=therapeutic)' corpus/01
etc.

Solução 1.

Por extrair texto entre duas strings em linhas diferentes

sed -n '/Subject: /{:a;N;/Message-ID:/!ba; s/\n/ /g; s/\s\s*/ /g; s/.*Subject: \|Message-ID:.*//g;p}' corpus/01

que dá

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]                              

Solução 2. *

Por Como posso substituir uma nova linha (\ n) usando sed?

sed ':a;N;$!ba;s/\n/ /g' corpus/01

substituirá as novas linhas por um espaço.

Encadeando isso com A2 em Como usar o sed / grep para extrair texto entre duas palavras? , Nós temos:

sed ':a;N;$!ba;s/\n/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

que dá

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular  link in major cell growth pathway: Findings point to new potential  therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is  Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as  a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway  identified [Lysosomal amino acid transporter SLC38A9 signals arginine  sufficiency to mTORC1]] 

Essa variante remove espaços duplos:

sed ':a;N;$!ba;s/\n/ /g; s/\s\s*/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

dando

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]

1
boa aventura :))
Alexandru-Mihai Manolescu
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.