¿Cómo usar sed / grep para extraer texto entre dos palabras?

134

Estoy tratando de generar una cadena que contiene todo entre dos palabras de una cadena:

entrada:

"Here is a String"

salida:

"is a"

Utilizando:

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

incluye los puntos finales, pero no quiero incluirlos.

usuario1190650
fuente
8
¿Cuál debería ser el resultado si la entrada es Here is a Here String? O I Hereby Dub Thee Sir Stringy?
ghoti
55
FYI. Su comando significa imprimir todo entre la línea que tiene la palabra Aquí y la línea que tiene la palabra Cadena, no lo que desea.
Hai Vu
Las otras sedpreguntas frecuentes comunes son "¿cómo puedo extraer texto entre líneas particulares"; esto es stackoverflow.com/questions/16643288/…
tripleee

Respuestas:

109
sed -e 's/Here\(.*\)String/\1/'
Brian Campbell
fuente
2
¡Gracias! ¿Qué pasa si quisiera encontrar todo lo que hay entre "uno es" y "Cadena" en "Aquí hay un es una cadena"? (sed -e 's / one es (. *) Cadena / \ 1 /'?
usuario1190650
55
@ user1190650 Eso funcionaría si quieres ver el "Aquí hay un" también. Puede probarlo: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. Si lo que desea es la parte entre "es" y "Cadena", entonces usted necesita para hacer que la expresión regular coincide con toda la línea: sed -e 's/.*one is\(.*\)String.*/\1/'. En sed, s/pattern/replacement/diga "sustituir 'reemplazo' por 'patrón' en cada línea". Solo cambiará cualquier cosa que coincida con el "patrón", por lo que si desea que reemplace toda la línea, debe hacer que el "patrón" coincida con toda la línea.
Brian Campbell el
9
Esto se rompe cuando la entrada esHere is a String Here is a String
Jay D
1
Sería genial ver la solución para un caso: "Aquí hay una cadena de bla bla bla Aquí hay 1 una cadena de bla bla bla Aquí hay 2 una salida de blash blash String" debería recoger solo la primera subcadena entre Aquí y String "
Jay D
1
@JayD sed no admite coincidencias no codiciosas, consulte esta pregunta para conocer algunas alternativas recomendadas.
Brian Campbell
180

GNU grep también puede admitir una mirada hacia adelante y hacia atrás positiva y negativa: para su caso, el comando sería:

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

Si hay múltiples ocurrencias de Herey string, puede elegir si desea hacer coincidir entre el primero Herey el último stringo hacerlas coincidir individualmente. En términos de expresiones regulares, se llama coincidencia codiciosa (primer caso) o coincidencia no codiciosa (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
fuente
31
Tenga en cuenta que la -Popción de GNU grep no existe en el grepincluido en * BSD, o los que vienen con cualquier SVR4 (Solaris, etc.). En FreeBSD, puede instalar el devel/pcrepuerto que incluye pcregrep, que admite PCRE (y mirar hacia adelante / atrás). Las versiones anteriores de OSX usaban GNU grep, pero en OSX Mavericks, -Pse deriva de la versión de FreeBSD, que no incluye la opción.
ghoti
1
Hola, ¿cómo extraigo solo contenido distinto?
Durgesh Suthar
44
Esto no funciona porque si la cadena final "cadena" aparece más de una vez, obtendrá la última aparición, no la siguiente .
Buttle Butkus
66
En el caso de Here is a string a string, tanto " is a " y " is a string a "son respuestas válidas (ignorar las comillas), según las necesidades de interrogación. Depende de usted cuál de estos que quieren y luego la respuesta puede ser diferente en consecuencia. De todos modos, para su requerimiento, esto funcionará:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane
2
@BND, debe habilitar la función de búsqueda multilínea de pcregrep . echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
Anishsane
58

La respuesta aceptada no elimina el texto que podría ser anterior Hereo posterior String. Esta voluntad:

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

La principal diferencia es la adición de .*inmediatamente antes Herey después String.

rodador
fuente
Tu respuesta es prometedora. Sin embargo, un problema. ¿Cómo puedo extraerlo a la primera cadena vista si hay varias cadenas en la misma línea? Gracias
Mian Asbat Ahmad
@MianAsbatAhmad Desearía hacer que el *cuantificador sea entre Herey Stringno codicioso (o vago). Sin embargo, el tipo de expresión regular utilizada por sed no admite cuantificadores perezosos ( ?inmediatamente después .*) de acuerdo con esta pregunta de Stackoverflow. Por lo general, para implementar un cuantificador diferido, solo se compararía con todo, excepto el token que no deseaba coincidir, pero en este caso, no hay un solo token, sino una cadena completa String.
Wheeler
Gracias, obtuve la respuesta usando awk, stackoverflow.com/questions/51041463/…
Mian Asbat Ahmad
Desafortunadamente, esto no funciona si la cadena tiene saltos de línea
Witalo Benicio
No se supone que lo haga. .no coincide con los saltos de línea. Si desea hacer coincidir los saltos de línea, puede reemplazarlos .con algo como [\s\s].
Wheeler
35

Puedes quitar cadenas solo en Bash :

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

Y si tiene un GNU grep que incluye PCRE , puede usar una aserción de ancho cero:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a
ghoti
fuente
¿Por qué este método es tan lento? cuando se elimina una página html grande con este método, tarda unos 10 segundos.
Adam Johns
@ AdamJohns, ¿qué método? ¿El PCRE? PCRE es bastante complejo de analizar, pero 10 segundos parece extremo. Si le preocupa, le recomiendo que haga una pregunta que incluya un código de ejemplo y vea lo que dicen los expertos.
ghoti
Creo que fue muy lento para mí porque contenía una fuente de un archivo html muy grande en una variable. Cuando escribí el contenido en el archivo y luego analicé el archivo, la velocidad aumentó dramáticamente.
Adam Johns
22

A través de GNU awk,

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

El parámetro grep with -P( perl-regexp ) admite \K, lo que ayuda a descartar los caracteres coincidentes previamente. En nuestro caso, la cadena previamente emparejada fue Heredescartada del resultado 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 

Si quieres que la salida sea, is aentonces puedes probar lo siguiente,

$ 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
fuente
Esto no funciona para: echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}', sólo se devuelve is aen lugar de debe ser is a is a@Avinash Raj
Alper
20

Si tiene un archivo largo con muchas ocurrencias de varias líneas, es útil imprimir primero las líneas numéricas:

cat -n file | sed -n '/Here/,/String/p'
alemol
fuente
3
¡Gracias! Esta es la única solución que funcionó en mi caso (archivo de texto de varias líneas, en lugar de una sola cadena sin saltos de línea). Obviamente, para tenerlo sin numeración de línea, la -nopción catdebe omitirse.
Jeffrey Lebowski
... en cuyo caso catse puede omitir por completo; sedsabe leer un archivo o entrada estándar.
tripleee
9

Esto podría funcionar para usted (GNU sed):

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

Esto presenta cada representación de texto entre dos marcadores (en este caso Herey String) en una nueva línea y conserva nuevas líneas dentro del texto.

potong
fuente
7

Todas las soluciones anteriores tienen deficiencias donde la última cadena de búsqueda se repite en otra parte de la cadena. Me pareció mejor escribir una función 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
fuente
6

Puedes usar dos comandos s

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

Tambien 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
fuente
6

Para entender el sedcomando, tenemos que construirlo paso a paso.

Aquí está tu texto original

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

Intentemos eliminar Here cadena con la sopción de ubicación ensed

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

En este punto, creo que sería capaz de eliminar String, así

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

Pero esta no es su salida deseada.

Para combinar dos comandos sed, use -e opción

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

Espero que esto ayude

Sabrina
fuente
4

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

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

El contenido que está dentro de los corchetes se almacenará como \1.

mvairavan
fuente
Esto elimina cadenas en lugar de generar algo intermedio. Intente eliminar "Hola" con "is" en el comando sed y generará "Hola a"
Jonathan
1

Problema. Mis mensajes de Claws Mail almacenados se envuelven de la siguiente manera, y estoy tratando de extraer las líneas de Asunto:

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>

Según A2 en este hilo, ¿Cómo usar sed / grep para extraer texto entre dos palabras? la primera expresión, a continuación, "funciona" siempre que el texto coincidente no contenga una nueva línea:

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

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

Sin embargo, a pesar de probar numerosas variantes ( .+?; /s; ...), no pude hacer que funcionen:

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

Solución 1

Por Extraer texto entre dos cadenas en diferentes líneas

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

lo que da

[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]]                              

Solución 2. *

Per ¿Cómo puedo reemplazar una nueva línea (\ n) usando sed?

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

reemplazará las nuevas líneas con un espacio.

Encadenando eso con A2 en ¿Cómo usar sed / grep para extraer texto entre dos palabras? , obtenemos:

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

lo que da

[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]] 

Esta variante elimina los espacios dobles:

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
fuente
1
bonita aventura :))
Alexandru-Mihai Manolescu