regex named group if exist - python

Good morning,
I have a string that I need to parse and print the content of two named group knowing that one might not exist.
The string looks like this (basically content of /proc/pid/cmdline):
"""
<some chars with letters / numbers / space / punctuation> /CLASS_NAME:myapp.server.starter.StarterHome /PARAM_XX:value_XX /PARAM_XX:value_XX /CONFIG_FILE:myapp.server.config.myconfig.txt /PARAM_XX:value_XX /PARAM_XX:value_XX /PARAM_XX:value_XX <some chars with letters / numbers / space / punctuation>
"""
my processes have almost the same pattern, that is:
/CLASS_NAME:myapp.server.starter.StarterHome is always present, but
/CONFIG_FILE:myapp.server.config.myconfig.txt is NOT always present.
I'm using python2 with re module to catch the values. So far my pattern looks like this and I'm able to catch the value I want corresponding to /CLASS_NAME
re.compile('CLASS_NAME:\w+\W\w+\W\w+\W(?P<class>\w+)')
The because /CONFIG_FILE is present or not, I added the following to myregexp:
re.compile(r"""CLASS_NAME:\w+\W\w+\W\w+\W(?P<class>\w+).*?
(CONFIG_FILE:\w+\W\w+\W\w+\W(?P<cnf>\w+.txt))?
""", re.X)
My understanding is that the second part of my rexexp is optional because the whole part is between parenthesis followed by ?.
Unfortunately my assumption is wrong as it couldn't catch it
I also tried by removing the 1st ? but it didn't help.
I gave several tries through PYTHEX to try to understand my regexp but couldn't find a solution.
Could anyone have any suggestion to resolve my case?

You can wrap the whole optional part within an optional non-capturing group and make the capturing group for CONFIG_FILE obligatory:
re.compile(r"""CLASS_NAME:(?:\w+\W+){3}(?P<class>\w+)(?:.*?
(CONFIG_FILE:(?:\w+\W+){3}(?P<cnf>\w+\.txt)))?
""", re.X)
In case there are newlines, use re.X | re.S modifier options. Note that \w+\W\w+\W\w+\W is better written as (?:\w+\W+){3}.
See the regex demo
The main difference is (?:.*?(CONFIG_FILE:(?:\w+\W+){3}(?P<cnf>\w+\.txt)))? part:
(?: - start of an optional (as there is a greedy ? quantifier after it) non-capturing group matching
.*? - any 0+ chars, as few as possible
(CONFIG_FILE:(?:\w+\W+){3}(?P<cnf>\w+\.txt)) - matches
CONFIG_FILE: - a literal substring
(?:\w+\W+){3} - three sequences of 1+ word chars followed with 1+ non-word chars
(?P<cnf>\w+\.txt) - Group cnf: 1+ word chars, a dot (note it should be escaped) and then txt
)? - end of the optional non-capturing group (that will be tried once)

Related

regex non greedy quantifier catching nothing, greedy catching too much

I'm writing a python regex formula that parses the content of a heading, however the greedy quantifier is not working well, and the non greedy quantifier is not working at all.
My string is
Step 1 Introduce The Assets:
Step2 Verifying the Assets
Step 3Making sure all the data is in the right place:
What I'm trying to do is extract the step number, and the heading, excluding the :.
Now I've tried multiple regex string and came up with these 2:
r1 = r"Step ?([0-9]+) ?(.*) ?:?"
r2 = r"Step ?([0-9]+) ?(.*?) ?:?"
r1 is capturing the step number, but is also capturing : at the end.
r2 is capturing the step number, and ''. I'm not sure how to handle the case where there is a .* followed by a string.
Necessary Edit:
The heading might contain : inside the string, I just want to ignore the trailing one. I know I can strip(':') but I want to understand what I'm doing wrong.
You can write the pattern using a negated character class without the non greedy and optional parts using a negated character class:
\bStep ?(\d+) ?([^:\n]+)
\bStep ? Match the word Step and optional space
(\d+) ? Capture 1+ digits in group 1 followed by matching an optional space
([^:\n]+) Capture 1+ chars other than : or a newline in group 2
Regex demo
If the colon has to be at the end of the string:
\bStep ?(\d+) ?([^:\n]+):?$
Regex demo

Regex optional character with a conditional clause

I have a regex problem that combines the ideas of optional characters and conditional regex statements that I'm unsure how to solve.
I want to find a pattern that, in addition to matching an initial number, will also match the following uppercase letter if and only if that character is not followed by a lowercase letter. The string will only ever have one number. For example:
'fds;o2Ko ' ==> '2'
'rejy 3ked' ==> '3'
's.fg6G hb' ==> '6G'
'3M- gfafg' ==> '3M'
'dgfAN adg' ==> no pattern found
I've tried various combinations of conditional statements but can't seem to combine the concepts properly. I'm working in python using the following code:
pattern = r'[1-9][A-Z]?'
ID = str(re.findall(pattern, 's.fg6G hb')).strip('[]\'')
The above is what I want without the conditional statement. I'm unsure how to include an appropriate conditional statement. I think pattern would be something like r'[1-9](?(?=[A-Z][a-z])[A-Z]?|)' but don't understand how I can look ahead beyond the current character.
It seems you can try to use:
\d+(?:[A-Z](?![a-z]))?
See the online demo
\d+ - 1+ Digits.
(?: - Open non-capture group:
[A-Z] - A single uppercase alpha.
(?![a-z]) - A nested negative lookahead to assert position is not followed by a lowercase alpha.
)? - Close non-capture group and make it optional.

How to not capture a group in regex if it is followed by an another group

If I have a string eg.: 'hcto,231' or 'hcto.12' I want to be able to capture 'o,231' or 'o.12' and process it as a number ('hct' is random and any other string can replace it).
But I don't want to capture if the 'o' character if followed by a decimal number eg: 'wordo.23.12' or 'wordo,23,12'.
I've tried using the following regex:
([oO][.,][0-9]+)(?!([.,][0-9]+))
but it always matches.
In the string 'hcto.22.23' it matches the bold part, but I don't want it to match anything. Is there a way to combine groups so it won't match if the negative lookahead is true.
The match occurs in hcto.22.23 because the lookahead triggers backtracking, and since [0-9]+ match match a single 2 (it does not have to match 22) the match succeeds and returns a smaller, unexpected match:
It seems the simplest way to fix the current issue is to make the dot or comma pattern in the lookahead optional, and remove unnecessary groups:
[oO][.,]\d+(?![.,]?\d)
See the regex demo.
Details
[oO] - o or O
[.,] - a dot or comma
\d+ - one or more digits
(?![.,]?\d) - not followed with ./, and a digit, or just with a digit.

How to remove a word if it has more than 2 occurrence of a given character in python?

I am parsing a log file which has lines like:
Pushing the logs into /var/log/my_log.txt
Pushing the logs into /opt/test/log_file.txt
There are multiple occurrences of these lines with auto-generated paths(/.../.../...)
I want to change this into a generic form like:
Pushing the logs into PATH
I tried using regex to select a word with multiple forward slashes and then replace it with the word 'PATH' as follows:
line = re.sub(r'\b([\/A-Z]*\/[A-Z]*){1,}\b',' PATH ',line)
Only the forward slashes are getting replaced but not the entire word.
Very new to this concept. Am I doing something wrong? All help is appreciated. Thanks.
You could use:
import re
line = 'Pushing the logs into /var/log/my_log.txt'
pat = r'(?<!\S)(/\S+){2,}'
line = re.sub(pat, 'PATH', line)
print(line)
This is not answering exactly as stated because it looks for "words" that must start with a / and also contain two or more / (with other non-whitespace characters following each /) -- so it would cover e.g. /tmp/my_log.txt. I think this better covers the sort of strings that you would find -- if they are absolute paths then / will always be the first character, and similarly if they are files rather than directories then the last / will not be at the end (although I haven't bothered to exclude a / at the end provided that there are also at least two before it). If you only want to look for e.g. 3 or more / (not at the end), then change the 2 to a 3, but you will miss /tmp/my_log.txt if you do that.
The first bit of the regexp (?<!\S) is a negative lookbehind assertion meaning "not preceded by a non-whitespace character", i.e. it will match at the start of a "word" or the start of the line. The next bit (/\S+) means a / followed by one or more non-whitespace characters (which could include / -- it doesn't matter so I haven't bothered to exclude these). And the {2,} means that there should be two or more of these.
(I am using "word" here as in the question, to refer to sequence of non-whitespace characters, not necessarily letters.)
Only the forward slashes are matched because the string is lower case, and the pattern matches zero or more times either a forward slash or uppercase char A-Z using [\/A-Z]*
You could make the pattern case insensitive using re.IGNORECASE but it will not match the underscore and the dot in the example data.
The first forward slash does not get matched as you start the pattern with a word boundary \b, but there is no word boundary between the space and the first forward slash.
A bit more specific match could be using \w to match a word character and specify the dot for the extension:
(?<!\S)(?:/\w+)+/\w+\.\w+(?!\S)
(?<!\S) Assert a whitespace boundary to the left
(?:/\w+)+ Match 1 or more times a / followed by 1+ word chars
/\w+\.\w+ Match the last / followed by a filename format using the dot and word chars
(?!\S) Assert a whitespace boundary to the right
See a regex demo | Python demo
import re
line = 'Pushing the logs into /var/log/my_log.txt'
line = re.sub(r'(?<!\S)(?:/\w+)+/\w+\.\w+(?!\S)', 'PATH', line)
print(line)
Output
Pushing the logs into PATH
A broader pattern could be matching 2 times the forward slash and use a negated character class to match any char except a forward slash or a newline
(?<!\S)(?:/[^/\r\n]+){2,}
See another regex demo

Regex match for non hyphenated words

I am trying to create a regex expression in Python for non-hyphenated words but I am unable to figure out the right syntax.
The requirements for the regex are:
It should not contain hyphens AND
It should contain atleast 1 number
The expressions that I tried are:=
^(?!.*-)
This matches all non-hyphenated words but I am not able to figure out how to additionally add the second condition.
^(?!.*-(?=/d{1,}))
I tried using double lookahead but I am not sure about the syntax to use for it. This matches ID101 but also matches STACKOVERFLOW
Sample Words Which Should Match:
1DRIVE , ID100 , W1RELESS
Sample Words Which Should Not Match:
Basically any non-numeric string (like STACK , OVERFLOW) or any hyphenated words (Test-11 , 24-hours)
Additional Info:
I am using library re and compiling the regex patterns and using re.search for matching.
Any assistance would be very helpful as I am new to regex matching and am stuck on this for quite a few hours.
Maybe,
(?!.*-)(?=.*\d)^.+$
might simply work OK.
Test
import re
string = '''
abc
abc1-
abc1
abc-abc1
'''
expression = r'(?m)(?!.*-)(?=.*\d)^.+$'
print(re.findall(expression, string))
Output
['abc1']
If you wish to simplify/modify/explore the expression, it's been explained on the top right panel of regex101.com. If you'd like, you can also watch in this link, how it would match against some sample inputs.
RegEx Circuit
jex.im visualizes regular expressions:
RegEx 101 Explanation
/
(?!.*-)(?=.*\d)^.+$
/
gm
Negative Lookahead (?!.*-)
Assert that the Regex below does not match
.* matches any character (except for line terminators)
* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)
- matches the character - literally (case sensitive)
Positive Lookahead (?=.*\d)
Assert that the Regex below matches
.* matches any character (except for line terminators)
* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)
\d matches a digit (equal to [0-9])
^ asserts position at start of a line
.+ matches any character (except for line terminators)
+ Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
$ asserts position at the end of a line
Global pattern flags
g modifier: global. All matches (don't return after first match)
m modifier: multi line. Causes ^ and $ to match the begin/end of each line (not only begin/end of string)
I came up with -
^[^-]*\d[^-]*$
so we need at LEAST one digit (\d)
We need the rest of the string to contain anything BUT a - ([^-])
We can have unlimited number of those characters, so [^-]*
but putting them together like [^-]*\d would fail on aaa3- because the - comes after a valid match- lets make sure no dashes can sneak in before or after our match ^[-]*\d$
Unfortunately that means that aaa555D fails. So we actually need to add the first group again- ^[^-]*\d[^-]$ --- which says start - any number of chars that aren't dashes - a digit - any number of chars that aren't dashes - end
Depending on style, we could also do ^([^-]*\d)+$ since the order of the digits/numbers dont matter, we can have as many of those as we want.
However, finally... this is how I would ACTUALLY solve this particular problem, since regexes may be powerful, but they tend to make the code harder to understand...
if ("-" not in text) and re.search("\d", text):

Categories

Resources