Comment extraire la sous-chaîne entre deux marqueurs?


335

Disons que j'ai une chaîne 'gfgfdAAA1234ZZZuijjk'et que je veux extraire uniquement la '1234'partie.

Je sais seulement quels seront les quelques personnages directement avant AAAet après ZZZla partie qui m'intéresse 1234.

Avec, sedil est possible de faire quelque chose comme ça avec une chaîne:

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

Et cela me donnera 1234comme résultat.

Comment faire la même chose en Python?

Réponses:


588

Utilisation d'expressions régulières - documentation pour référence ultérieure

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

20
La deuxième solution est meilleure, si le motif correspond la plupart du temps, car il est plus facile de demander pardon que permission. .
Bengt

7
L'indexation ne commence-t-elle pas à 0? Vous devez donc utiliser le groupe (0) au lieu du groupe (1)?
Alexander

22
@Alexander, non, le groupe (0) renverra la chaîne de correspondance complète: AAA1234ZZZ, et le groupe (1) renverra uniquement les caractères correspondant au premier groupe: 1234
Yurii K,

1
@Bengt: Pourquoi ça? La première solution me semble assez simple et comporte moins de lignes de code.
HelloGoodbye

5
Dans cette expression, le? modifie le + pour qu'il soit non gourmand, c'est-à-dire. il correspondra à n'importe quel nombre de fois à partir de 1 mais aussi peu que possible, ne s'étendant que si nécessaire. sans le?, le premier groupe correspondrait à gfgfAAA2ZZZkeAAA43ZZZife comme 2ZZZkeAAA43, mais avec le? il ne correspondrait qu'au 2, puis rechercher plusieurs (ou le faire supprimer et rechercher à nouveau) correspondrait au 43.
Dom

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

Ensuite, vous pouvez également utiliser des expressions rationnelles avec le module re, si vous le souhaitez, mais ce n'est pas nécessaire dans votre cas.


9
La question semble impliquer que le texte saisi contiendra toujours "AAA" et "ZZZ". Si ce n'est pas le cas, votre réponse échoue horriblement (j'entends par là qu'elle renvoie quelque chose de complètement faux au lieu d'une chaîne vide ou de lever une exception; pensez "bonjour là" comme chaîne d'entrée).
tzot

@ user225312 Mais la reméthode n'est-elle pas plus rapide?
confused00

1
Voteup, mais j'utiliserais "x = 'AAA'; s.find (x) + len (x)" au lieu de "s.find ('AAA') + 3" pour la maintenabilité.
Alex

1
Si l'un des jetons est introuvable dans le s, s.findil reviendra -1. l'opérateur de découpage s[begin:end] l'acceptera comme index valide et renverra la sous-chaîne indésirable.
ribamar

@ confused00 find est beaucoup plus rapide que re stackoverflow.com/questions/4901523/…
Claudiu Creanga

65

expression régulière

import re

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

La situation ci-dessus échouera avec un AttributeErrors'il n'y a pas de "AAA" et "ZZZ" dansyour_text

méthodes de chaîne

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

Ce qui précède retournera une chaîne vide si "AAA" ou "ZZZ" n'existent pas your_text.

Défi PS Python?


6
Cette réponse mérite probablement plus de votes positifs. La méthode des chaînes est la méthode la plus robuste. Il n'a pas besoin d'essayer / sauf.
ChaimG

... sympa, bien que limité. la partition n'est pas basée sur des expressions rationnelles, elle ne fonctionne donc que dans ce cas car la chaîne de recherche était limitée par des littéraux fixes
GreenAsJade

Merci beaucoup! - cela fonctionne pour les cordes et ne nécessite pas d'expression régulière
Alex

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

1
AttributeError: 'NoneType' object has no attribute 'groups'- s'il n'y a pas d'AAA, ZZZ dans la chaîne ...
eumiro

12

Surpris que personne n'ait mentionné cela, qui est ma version rapide pour les scripts uniques:

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

@ user1810100 a mentionné essentiellement que presque exactement 5 ans jour pour jour avant de publier ceci ...
John

10

vous pouvez utiliser une seule ligne de code

>>> import re

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

>>> ['1234']

le résultat recevra la liste ...


8

Vous pouvez utiliser le module re pour cela:

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

5

Avec sed, il est possible de faire quelque chose comme ça avec une chaîne:

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

Et cela me donnera 1234 en conséquence.

Vous pouvez faire de même avec une re.subfonction utilisant la même expression régulière.

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

Dans sed de base, le groupe de capture est représenté par \(..\), mais en python, il était représenté par (..).


5

En python, l'extraction de la chaîne de formulaire de sous-chaîne peut être effectuée à l'aide de la findallméthode du remodule d' expression régulière ( ).

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

4

Vous pouvez trouver la première sous-chaîne avec cette fonction dans votre code (par index de caractères). En outre, vous pouvez trouver ce qui se trouve après une sous-chaîne.

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

Donne

string

2

Juste au cas où quelqu'un devra faire la même chose que moi. J'ai dû tout extraire entre parenthèses en ligne. Par exemple, si j'ai une phrase comme «le président américain (Barack Obama) a rencontré ...» et que je veux obtenir uniquement «Barack Obama», c'est la solution:

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

C'est-à-dire que vous devez bloquer les parenthèses avec slash \ signe. Bien qu'il s'agisse d'un problème d'expressions plus régulières que Python.

De plus, dans certains cas, vous pouvez voir des symboles «r» avant la définition de l'expression régulière. S'il n'y a pas de préfixe r, vous devez utiliser des caractères d'échappement comme en C. Voici plus de discussion à ce sujet.


2

Utilisation de 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)

ce qui donne:

[['1234']]


0

Voici une solution sans regex qui tient également compte des scénarios où la première sous-chaîne contient la deuxième sous-chaîne. Cette fonction ne trouvera une sous-chaîne que si le deuxième marqueur est après le premier marqueur.

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

Une autre façon de le faire est d'utiliser des listes (en supposant que la sous-chaîne que vous recherchez est composée uniquement de nombres):

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

Un liners qui renvoie une autre chaîne s'il n'y a pas de correspondance. Edit: la version améliorée utilise la nextfonction, remplacez "not-found"par quelque chose d'autre si nécessaire:

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

Mon autre méthode pour le faire, moins optimale, utilise la 2e expression régulière, n'a toujours pas trouvé de moyen plus court:

import re
res = ( ( re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk") or re.search("()","") ).group(1) )
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.