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.

user1190650
fonte
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/'
Brian Campbell
fonte
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 
anishsane
fonte
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.

veículo com rodas
fonte
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
ghoti
fonte
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
Avinash Raj
fonte
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'
alemol
fonte
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.

potong
fonte
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"
Gary Dean
fonte
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 
Ivan
fonte
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

Sabrina
fonte
4

Você pode usar \1(consulte http://www.grymoire.com/Unix/Sed.html#uh-4 ):

echo "Hello is a String" | sed 's/Hello\(.*\)String/\1/g'

O conteúdo que está dentro dos colchetes será armazenado como \1.

mvairavan
fonte
Isso remove as strings em vez de produzir algo no meio. Tente remover "Hello" com "is" no comando sed e ele exibirá "Hello a" #
Jonathan Jonathan
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]]
Victoria Stuart
fonte
1
boa aventura :))
Alexandru-Mihai Manolescu