Wie extrahiere ich den Teilstring zwischen zwei Markern?


334

Angenommen, ich habe eine Zeichenfolge 'gfgfdAAA1234ZZZuijjk'und möchte nur den '1234'Teil extrahieren .

Ich weiß nur, was die wenigen Charaktere direkt vor AAAund nach ZZZdem Teil sein werden, an dem ich interessiert bin 1234.

Damit ist sedes möglich, so etwas mit einem String zu machen:

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

Und das wird mir 1234als Ergebnis geben.

Wie mache ich dasselbe in Python?

Antworten:


585

Verwendung regulärer Ausdrücke - Dokumentation zur weiteren Bezugnahme

import re

text = 'gfgfdAAA1234ZZZuijjk'

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

# found: 1234

oder:

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
Die zweite Lösung ist besser, wenn das Muster die meiste Zeit übereinstimmt, da es einfacher ist, um Vergebung zu bitten als um Erlaubnis. .
Bengt

7
Beginnt die Indizierung nicht bei 0? Sie müssten also Gruppe (0) anstelle von Gruppe (1) verwenden?
Alexander

22
@Alexander, nein, Gruppe (0) gibt eine vollständig übereinstimmende Zeichenfolge zurück: AAA1234ZZZ, und Gruppe (1) gibt nur Zeichen zurück, die mit der ersten Gruppe übereinstimmen: 1234
Yurii K

1
@ Bengt: Warum ist das so? Die erste Lösung sieht für mich recht einfach aus und enthält weniger Codezeilen.
HelloGoodbye

5
In diesem Ausdruck ist das? ändert das + so, dass es nicht gierig ist, dh. Es wird beliebig oft von 1 aufwärts, aber so wenig wie möglich übereinstimmen und nur bei Bedarf erweitert. ohne das? würde die erste Gruppe gfgfAAA2ZZZkeAAA43ZZZonife als 2ZZZkeAAA43 abgleichen, aber mit dem? es würde nur mit der 2 übereinstimmen, dann würde die Suche nach mehreren (oder das Entfernen und erneute Suchen) mit der 43 übereinstimmen.
Dom

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

Dann können Sie Regexps auch mit dem Re-Modul verwenden, wenn Sie möchten, aber das ist in Ihrem Fall nicht erforderlich.


9
Die Frage scheint zu implizieren, dass der Eingabetext immer sowohl "AAA" als auch "ZZZ" enthält. Wenn dies nicht der Fall ist, schlägt Ihre Antwort schrecklich fehl (damit meine ich, dass etwas völlig Falsches anstelle einer leeren Zeichenfolge zurückgegeben oder eine Ausnahme ausgelöst wird; denken Sie "Hallo" als Eingabezeichenfolge).
Zot

@ user225312 Ist die reMethode aber nicht schneller?
verwirrt00

1
Abstimmung, aber ich würde "x = 'AAA'; s.find (x) + len (x)" anstelle von "s.find ('AAA') + 3" für die Wartbarkeit verwenden.
Alex

1
Wenn einer der Token im nicht gefunden werden kann s, s.findwird er zurückgegeben -1. Der Slicing-Operator s[begin:end] akzeptiert ihn als gültigen Index und gibt unerwünschte Teilzeichenfolgen zurück.
Ribamar

@ confused00 find ist viel schneller als re stackoverflow.com/questions/4901523/…
Claudiu Creanga

63

regulären Ausdruck

import re

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

Das obige Ist wird mit einem fehlschlagen AttributeError Ist wenn keine "AAA" und "ZZZ" vorhanden sindyour_text

Zeichenfolgenmethoden

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

Das Obige gibt eine leere Zeichenfolge zurück, wenn entweder "AAA" oder "ZZZ" in nicht vorhanden ist your_text .

PS Python Challenge?


6
Diese Antwort verdient wahrscheinlich mehr Stimmen. Die String-Methode ist der robusteste Weg. Es braucht keinen Versuch / außer.
ChaimG

... schön, wenn auch begrenzt. Partition ist nicht Regex-basiert, so funktioniert es nur in diesem Fall, weil die
Suchzeichenfolge

Großartig, vielen Dank! - Dies funktioniert für Zeichenfolgen und erfordert keine Regex
Alex

Oh mein Gott! wirklich, Partition! Danke vielmals!
Andrey Wal

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

1
AttributeError: 'NoneType' object has no attribute 'groups'- Wenn es keine AAA gibt, ZZZ in der Zeichenfolge ...
Eumiro

12

Überrascht, dass niemand dies erwähnt hat, was meine Schnellversion für einmalige Skripte ist:

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

@ user1810100 erwähnte im Wesentlichen, dass fast genau 5 Jahre bis zu dem Tag, bevor Sie dies gepostet haben ...
John

10

Sie können nur eine Codezeile verwenden

>>> import re

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

>>> ['1234']

Ergebnis erhält Liste ...


7

Sie können dafür das re- Modul verwenden:

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

5

Mit sed ist es möglich, so etwas mit einer Schnur zu machen:

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

Und das wird mir 1234 geben.

Sie können dasselbe mit re.subFunktionen tun , die denselben regulären Ausdruck verwenden.

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

In Basic Sed wird die Erfassungsgruppe durch dargestellt \(..\), in Python jedoch durch (..).


5

In Python kann das Extrahieren von Teilzeichenfolgen-Formularzeichenfolgen mithilfe der findallMethode im reModul regulärer Ausdruck ( ) erfolgen.

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

4

Den ersten Teilstring mit dieser Funktion finden Sie in Ihrem Code (nach Zeichenindex). Sie können auch finden, was nach einem Teilstring ist.

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

Gibt

string

2

Nur für den Fall, dass jemand das Gleiche tun muss wie ich. Ich musste alles in Klammern in einer Zeile extrahieren. Wenn ich zum Beispiel eine Zeile wie "US-Präsident (Barack Obama) hat sich getroffen mit ..." habe und nur "Barack Obama" erhalten möchte, ist dies eine Lösung:

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

Dh Sie müssen Klammern mit blockieren slash \ Vorzeichen . Obwohl es ein Problem mit reguläreren Ausdrücken als Python ist.

In einigen Fällen werden möglicherweise auch 'r'-Symbole vor der Regex-Definition angezeigt. Wenn es kein r-Präfix gibt, müssen Sie Escape-Zeichen wie in C verwenden. Hier finden Sie weitere Informationen dazu.


2

PyParsing verwenden

import pyparsing as pp

word = pp.Word(pp.alphanums)

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

was ergibt:

[['1234']]


0

Hier ist eine Lösung ohne regulären Ausdruck, die auch Szenarien berücksichtigt, in denen der erste Teilstring den zweiten Teilstring enthält. Diese Funktion findet nur dann einen Teilstring, wenn der zweite Marker hinter dem ersten Marker liegt.

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

Eine andere Möglichkeit besteht darin, Listen zu verwenden (vorausgesetzt, der gesuchte Teilstring besteht nur aus Zahlen):

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

Ein Liner, der eine andere Zeichenfolge zurückgibt, wenn keine Übereinstimmung vorliegt. Bearbeiten: Verbesserte Version verwendet nextFunktion, "not-found"bei Bedarf durch etwas anderes ersetzen :

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

Meine andere Methode, die weniger optimal ist, verwendet das zweite Mal Regex und hat immer noch keinen kürzeren Weg gefunden:

import re
res = ( ( re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk") or re.search("()","") ).group(1) )
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.