¿Cómo extraer la subcadena entre dos marcadores?


335

Digamos que tengo una cadena 'gfgfdAAA1234ZZZuijjk'y quiero extraer solo la '1234'parte.

Solo sé cuáles serán los pocos personajes directamente antes AAAy después de ZZZla parte que me interesa 1234.

Con sedesto es posible hacer algo como esto con una cadena:

echo "$STRING" | sed -e "s|.*AAA\(.*\)ZZZ.*|\1|"

Y esto me dará 1234como resultado.

¿Cómo hacer lo mismo en Python?

Respuestas:


588

Uso de expresiones regulares: documentación para mayor referencia

import re

text = 'gfgfdAAA1234ZZZuijjk'

m = re.search('AAA(.+?)ZZZ', text)
if m:
    found = m.group(1)

# found: 1234

o:

import re

text = 'gfgfdAAA1234ZZZuijjk'

try:
    found = re.search('AAA(.+?)ZZZ', text).group(1)
except AttributeError:
    # AAA, ZZZ not found in the original string
    found = '' # apply your error handling

# found: 1234

20
La segunda solución es mejor, si el patrón coincide la mayor parte del tiempo, porque es más fácil pedir perdón que permiso. .
Bengt

77
¿La indexación no comienza en 0? Entonces, ¿necesitaría usar group (0) en lugar de group (1)?
Alexander

22
@Alexander, no, el grupo (0) devolverá la cadena coincidente completa: AAA1234ZZZ, y el grupo (1) devolverá solo los caracteres coincidentes por el primer grupo: 1234
Yurii K

1
@Bengt: ¿Por qué es eso? La primera solución me parece bastante simple y tiene menos líneas de código.
HelloGoodbye

55
En esta expresión el? modifica el + para que no sea codicioso, es decir. coincidirá cualquier cantidad de veces desde 1 en adelante, pero lo menos posible, expandiéndose solo según sea necesario. sin el?, el primer grupo coincidiría con gfgfAAA2ZZZkeAAA43ZZZonife como 2ZZZkeAAA43, pero con el? solo coincidiría con el 2, luego buscar múltiples (o quitarlo y buscar de nuevo) coincidiría con el 43.
Dom

114
>>> s = 'gfgfdAAA1234ZZZuijjk'
>>> start = s.find('AAA') + 3
>>> end = s.find('ZZZ', start)
>>> s[start:end]
'1234'

Luego, también puede usar expresiones regulares con el módulo re, si lo desea, pero eso no es necesario en su caso.


99
La pregunta parece implicar que el texto de entrada siempre contendrá "AAA" y "ZZZ". Si este no es el caso, su respuesta falla horriblemente (con eso quiero decir que devuelve algo completamente incorrecto en lugar de una cadena vacía o arrojando una excepción; piense "hola allí" como cadena de entrada).
tzot

@ user225312 ¿Sin embargo, el remétodo no es más rápido?
confused00

1
Vote, pero usaría "x = 'AAA'; s.find (x) + len (x)" en lugar de "s.find ('AAA') + 3" para mantenerlo.
Alex

1
Si alguna de las fichas no se puede encontrar en el s, s.findvolverá -1. el operador de corte s[begin:end] lo aceptará como índice válido y devolverá una subcadena no deseada.
ribamar

@ confused00 find es mucho más rápido que re stackoverflow.com/questions/4901523/…
Claudiu Creanga

65

expresión regular

import re

re.search(r"(?<=AAA).*?(?=ZZZ)", your_text).group(0)

Lo anterior tal como está fallará con un AttributeErrorsi no hay "AAA" y "ZZZ" enyour_text

métodos de cadena

your_text.partition("AAA")[2].partition("ZZZ")[0]

Lo anterior devolverá una cadena vacía si "AAA" o "ZZZ" no existen your_text.

PS Python Challenge?


66
Esta respuesta probablemente merece más votos. El método de cadena es la forma más robusta. No necesita probar / excepto.
ChaimG

... agradable, aunque limitado. la partición no está basada en expresiones regulares, por lo que solo funciona en esta instancia porque la cadena de búsqueda estaba limitada por literales fijos
GreenAsJade

Genial, muchas gracias! - esto funciona para cadenas y no requiere expresiones regulares
Alex

15
import re
print re.search('AAA(.*?)ZZZ', 'gfgfdAAA1234ZZZuijjk').group(1)

1
AttributeError: 'NoneType' object has no attribute 'groups'- si no hay AAA, ZZZ en la cadena ...
eumiro

12

Sorprendido de que nadie haya mencionado esto, que es mi versión rápida para scripts únicos:

>>> x = 'gfgfdAAA1234ZZZuijjk'
>>> x.split('AAA')[1].split('ZZZ')[0]
'1234'

@ user1810100 mencionó esencialmente que casi exactamente 5 años antes del día en que publicaste esto ...
John

10

puedes hacerlo usando solo una línea de código

>>> import re

>>> re.findall(r'\d{1,5}','gfgfdAAA1234ZZZuijjk')

>>> ['1234']

resultado recibirá lista ...


8

Puede usar re module para eso:

>>> import re
>>> re.compile(".*AAA(.*)ZZZ.*").match("gfgfdAAA1234ZZZuijjk").groups()
('1234,)

5

Con sed es posible hacer algo como esto con una cadena:

echo "$STRING" | sed -e "s|.*AAA\(.*\)ZZZ.*|\1|"

Y esto me dará 1234 como resultado.

Podrías hacer lo mismo con la re.subfunción usando la misma expresión regular.

>>> re.sub(r'.*AAA(.*)ZZZ.*', r'\1', 'gfgfdAAA1234ZZZuijjk')
'1234'

En sed básico, el grupo de captura está representado por \(..\), pero en python sí (..).


5

En python, la extracción de la cadena de formulario de subcadena se puede hacer usando el findallmétodo en el remódulo de expresión regular ( )

>>> import re
>>> s = 'gfgfdAAA1234ZZZuijjk'
>>> ss = re.findall('AAA(.+)ZZZ', s)
>>> print ss
['1234']

4

Puede encontrar la primera subcadena con esta función en su código (por índice de caracteres). Además, puede encontrar lo que está después de una subcadena.

def FindSubString(strText, strSubString, Offset=None):
    try:
        Start = strText.find(strSubString)
        if Start == -1:
            return -1 # Not Found
        else:
            if Offset == None:
                Result = strText[Start+len(strSubString):]
            elif Offset == 0:
                return Start
            else:
                AfterSubString = Start+len(strSubString)
                Result = strText[AfterSubString:AfterSubString + int(Offset)]
            return Result
    except:
        return -1

# Example:

Text = "Thanks for contributing an answer to Stack Overflow!"
subText = "to"

print("Start of first substring in a text:")
start = FindSubString(Text, subText, 0)
print(start); print("")

print("Exact substring in a text:")
print(Text[start:start+len(subText)]); print("")

print("What is after substring \"%s\"?" %(subText))
print(FindSubString(Text, subText))

# Your answer:

Text = "gfgfdAAA1234ZZZuijjk"
subText1 = "AAA"
subText2 = "ZZZ"

AfterText1 = FindSubString(Text, subText1, 0) + len(subText1)
BeforText2 = FindSubString(Text, subText2, 0) 

print("\nYour answer:\n%s" %(Text[AfterText1:BeforText2]))

3
>>> s = '/tmp/10508.constantstring'
>>> s.split('/tmp/')[1].split('constantstring')[0].strip('.')

3
text = 'I want to find a string between two substrings'
left = 'find a '
right = 'between two'

print(text[text.index(left)+len(left):text.index(right)])

Da

string

2

En caso de que alguien tenga que hacer lo mismo que yo hice. Tuve que extraer todo dentro del paréntesis en una línea. Por ejemplo, si tengo una línea como 'el presidente de los Estados Unidos (Barack Obama) se reunió con ...' y quiero obtener solo 'Barack Obama', esta es la solución:

regex = '.*\((.*?)\).*'
matches = re.search(regex, line)
line = matches.group(1) + '\n'

Es decir, debe bloquear los paréntesis con slash \signo. Aunque es un problema sobre expresiones más regulares que Python.

Además, en algunos casos puede ver los símbolos 'r' antes de la definición de expresiones regulares. Si no hay un prefijo r, debe usar caracteres de escape como en C. Aquí hay más discusión sobre eso.


2

Usando PyParsing

import pyparsing as pp

word = pp.Word(pp.alphanums)

s = 'gfgfdAAA1234ZZZuijjk'
rule = pp.nestedExpr('AAA', 'ZZZ')
for match in rule.searchString(s):
    print(match)

cuyos rendimientos:

[['1234']]


0

Aquí hay una solución sin expresiones regulares que también tiene en cuenta los escenarios donde la primera subcadena contiene la segunda subcadena. Esta función solo encontrará una subcadena si el segundo marcador está detrás del primer marcador.

def find_substring(string, start, end):
    len_until_end_of_first_match = string.find(start) + len(start)
    after_start = string[len_until_end_of_first_match:]
    return string[string.find(start) + len(start):len_until_end_of_first_match + after_start.find(end)]

0

Otra forma de hacerlo es mediante listas (suponiendo que la subcadena que está buscando esté compuesta únicamente de números):

string = 'gfgfdAAA1234ZZZuijjk'
numbersList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
output = []

for char in string:
    if char in numbersList: output.append(char)

print(f"output: {''.join(output)}")
### output: 1234

-1

Uno de los revestimientos que devuelve otra cadena si no hubo coincidencia. Editar: la versión mejorada usa la nextfunción, reemplácela "not-found"por otra si es necesario:

import re
res = next( (m.group(1) for m in [re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk" ),] if m), "not-found" )

Mi otro método para hacer esto, menos óptimo, usa regex por segunda vez, aún no encontré una forma más corta:

import re
res = ( ( re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk") or re.search("()","") ).group(1) )
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.