Como extrair a substring entre dois marcadores?

334

Digamos que eu tenho uma string 'gfgfdAAA1234ZZZuijjk'e quero extrair apenas a '1234'parte.

Só sei quais serão os poucos personagens diretamente antes AAAe depois ZZZda parte em que estou interessado 1234.

Com sedisso é possível fazer algo assim com uma string:

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

E isso vai me dar 1234como resultado.

Como fazer a mesma coisa em Python?

miernik
fonte

Respostas:

586

Usando expressões regulares - documentação para referência adicional

import re

text = 'gfgfdAAA1234ZZZuijjk'

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

# found: 1234

ou:

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
eumiro
fonte
20
A segunda solução é melhor, se o padrão corresponder na maior parte do tempo, porque é mais fácil pedir perdão do que permissão. .
Bengt
7
A indexação não começa em 0? Então você precisaria usar o grupo (0) em vez do grupo (1)?
Alexander Alexander
22
@Alexander, não, grupo (0) retornará completa string combinada: AAA1234ZZZ e grupo (1) retornará apenas caracteres pareados por primeiro grupo: 1234
Yurii K
11
@Bengt: Por que isso? A primeira solução parece bastante simples para mim e possui menos linhas de código.
HelloGoodbye 07/07
5
Nesta expressão o? modifica o + para não ser ganancioso, ou seja. corresponderá a qualquer número de vezes de 1 em diante, mas o mínimo possível, apenas expandindo conforme necessário. sem o?, o primeiro grupo corresponderia a gfgfAAA2ZZZkeAAA43ZZZife como 2ZZZkeAAA43, mas com o? ele corresponderia apenas ao 2, em seguida, procurar por múltiplos (ou tê-lo retirado e pesquisar novamente) corresponderia ao 43.
Dom
113
>>> s = 'gfgfdAAA1234ZZZuijjk'
>>> start = s.find('AAA') + 3
>>> end = s.find('ZZZ', start)
>>> s[start:end]
'1234'

Então você pode usar regexps com o módulo re também, se desejar, mas isso não é necessário no seu caso.

Lennart Regebro
fonte
9
A questão parece sugerir que o texto de entrada sempre conterá "AAA" e "ZZZ". Se não for esse o caso, sua resposta falha terrivelmente (com isso quero dizer que retorna algo completamente errado, em vez de uma string vazia ou lança uma exceção; pense "olá, lá" como string de entrada).
tzot 6/02/11
@ user225312 O remétodo não é mais rápido?
confused00
11
Votar, mas eu usaria "x = 'AAA'; s.find (x) + len (x)" em vez de "s.find ('AAA') + 3" para manutenção.
21417 Alex
11
Se algum dos tokens não puder ser encontrado no s, s.findretornará -1. o operador de fatiamento s[begin:end] aceitará como índice válido e retornará substring indesejado.
Ribamar # 28/17
@ confused00 find é muito mais rápido que re stackoverflow.com/questions/4901523/…
Claudiu Creanga
63

expressão regular

import re

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

O acima como está falhará com um AttributeErrorse não houver "AAA" e "ZZZ" noyour_text

métodos de string

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

O exemplo acima retornará uma string vazia se "AAA" ou "ZZZ" não existirem your_text.

Desafio PS Python?

tzot
fonte
6
Essa resposta provavelmente merece mais votos positivos. O método string é a maneira mais robusta. Não precisa de uma tentativa / exceção.
precisa
... legal, embora limitado. partição não é regex base, por isso só funciona neste caso, porque a cadeia de pesquisa foi delimitada por literais fixos
GreenAsJade
Ótimo, muito obrigado! - Isso funciona para cordas e não requer regex
Alex
AMD! realmente, partição! Muito Obrigado!
Andrey Wal
15
import re
print re.search('AAA(.*?)ZZZ', 'gfgfdAAA1234ZZZuijjk').group(1)
infravermelho
fonte
11
AttributeError: 'NoneType' object has no attribute 'groups'- se não houver AAA, ZZZ na string ...
eumiro
12

Surpreso que ninguém tenha mencionado isso, que é minha versão rápida para scripts únicos:

>>> x = 'gfgfdAAA1234ZZZuijjk'
>>> x.split('AAA')[1].split('ZZZ')[0]
'1234'
Tio cabelo comprido
fonte
@ user1810100 mencionado, essencialmente, que quase exatamente 5 anos para o dia antes que você postou isso ...
John
10

você pode fazer usando apenas uma linha de código

>>> import re

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

>>> ['1234']

resultado receberá a lista ...

Mahesh Gupta
fonte
7

Você pode usar o módulo re para isso:

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

Com o sed, é possível fazer algo assim com uma string:

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

E isso me dará 1234 como resultado.

Você poderia fazer o mesmo com a re.subfunção usando o mesmo regex.

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

No sed básico, o grupo de captura é representado por \(..\), mas em python foi representado por (..).

Avinash Raj
fonte
5

Em python, a extração da string do formulário de substring pode ser feita usando o findallmétodo no remódulo expression ( ) regular .

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

Você pode encontrar a primeira substring com esta função no seu código (por índice de caracteres). Além disso, você pode encontrar o que está depois de uma substring.

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]))
Saeed Zahedian Abroodi
fonte
3
>>> s = '/tmp/10508.constantstring'
>>> s.split('/tmp/')[1].split('constantstring')[0].strip('.')
user1810100
fonte
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)])

string
Fernando Wittmann
fonte
2

Apenas no caso de alguém ter que fazer a mesma coisa que eu. Eu tive que extrair tudo dentro de parênteses em uma linha. Por exemplo, se eu tenho uma frase como 'Presidente dos EUA (Barack Obama) se reuniu com ...' e eu quero apenas 'Barack Obama', esta é a solução:

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

Ou seja, você precisa bloquear o parêntese com o slash \sinal. Embora seja um problema sobre expressões mais regulares que o Python.

Além disso, em alguns casos, você pode ver os símbolos 'r' antes da definição do regex. Se não houver prefixo r, você precisará usar caracteres de escape como em C. Aqui está mais discussão sobre isso.

Denis Kutlubaev
fonte
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)

que produz:

[['1234']]

Rafael
fonte
0

Aqui está uma solução sem regex que também considera os cenários em que a primeira subseqüência contém a segunda subseqüência. Esta função encontrará apenas uma substring se o segundo marcador estiver após o primeiro 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)]
Roymunson
fonte
0

Outra maneira de fazer isso é usar listas (supondo que a substring que você está procurando seja feita apenas 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
Julio Souto
fonte
-1

Forros um que retornam outra sequência se não houver correspondência. Edit: versão melhorada usa a nextfunção, substitua "not-found"por outra coisa, se necessário:

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

Meu outro método para fazer isso, menos otimizado, usa regex pela segunda vez, ainda não encontrou uma maneira mais curta:

import re
res = ( ( re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk") or re.search("()","") ).group(1) )
MaxLZ
fonte