google sheets regextract nth occurence on new line - regex

Example Data (all in one google sheet cell)
Test# 123
Bob# abc
how are you doing
John# test
... # ...
My goal is to return everything after the third # so in this example " test" I have fiddled with a lot of examples online but they seem to be incompatible with the google sheets version of regextract for some reason.
I can use (\n.*){4} to return the fourth line but that is no good because as you can see from the example data I do not know how many lines of data will be between a # and also that is not extracting from the # let alone the third #.
Goal: extract the third # to the end of the line including or excluding the # will do.
Here is an idea I have but surely the format is all butchered ((\n)(?=#.*)){3} I would expect this regex to grab the third # IF it was the beginning of the line but I can't even get that working let alone if it were to occur in the middle of the line was as was my example.

You may use
=REGEXEXTRACT(A1, "^(?:[^#]*#){3}(.+)")
To get all text from the 3rd till the 4th # char:
=REGEXEXTRACT(A1, "^(?:[^#]*#){3}([^#]+)")
Details
^ - start of string
(?:[^#]*#){3} - three occurrences of 0+ chars other than # and then # (the (?:...) is a non-capturing group, you need it to group a sequence of patterns without the need to return the text this group pattern matches)
(.+) - Capturing group 1 (REGEXEXTRACT returns the text captured into the group only if the group is specified): any 1+ chars other than line break chars
([^#]+) - this captures into Group 1 any one or more chars other than #.

also works:
=REGEXEXTRACT(INDEX(SPLIT(A1, "#"),,4), " (.+)")

Related

Using regex to parse/reduce text file

edit
I've realized I made a mistake when explaining myself. Apologies for that.
Most of the artifacts come from this path:
D:\Folder1\Folder2\Folder3\Folder4\Folder5\
then breaks into Artifact folders and its sub-folders like this:
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.0\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.1\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.2\data.xxx
I would appreciate help with following thing:
I have this list (around 5k rows) of paths to different artifacts and they have different versions, to give you an example:
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.0\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.1\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.2\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder2\Artifact\Artifact-1.1\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder2\Artifact\Artifact-1.2\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder2\Artifact\Artifact-1.3\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder3\Artifact\Artifact-1.2\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder3\Artifact\Artifact-1.3\data.xxx
And my goal to achieve is this:
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.0\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder2\Artifact\Artifact-1.1\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder3\Artifact\Artifact-1.2\data.xxx
Basically to scope it down to just 1 version.
I've tried using ^(.*)(\n\1)+$ and $1. but that obviously didn't work. So I was wondering if you have an idea how to approach this. Greatly appreciate help, thanks!
You can use
Find what: ^(.*\.)(\d+)\\[^\\\n]+(\n\1\d+\\[^\\\n]+)+$
Replace: $1$2\\
See the regex demo. Details:
^ - start of a line (it is the default ^ behavior in Visual Studio Code)
(.*\.) - Group 1: any one or more chars other than line break chars as many as possible and then a .
(\d+) - Group 2:
\\ - a \ char
[^\\\n]+ - one or more chars other than \ and a line break
(\n\1\d+\\[^\\\n]+)+ - Group 3 capturing one or more sequences of a line break and then the value captured into Group 1, one or more digits, a \ char and then one or more chars other than \ and a line break
$ - end of a line.
Here is another attempt, see regex101 demo.
The basic idea is to isolate someText-\d?. in capture group 2.
Then look for $2 in following lines. What precedes $2 or follows $2 in those following lines can vary.
Find: ^(.*\\(?=.*\\))(.*-\d+\.)(.*\\?.*)(\n.*\2.*)*
Replace: $1$2$3
So here is the most interesting part: ^(.*\\(?=.*\\))(.*-\d+\.)
This will get your Artifact-1. or Artifact-17. or someText-2. into capture group 2. Because using a positive lookahead (?=.*\\) the following group 2 (.*-\d+\.) will be in the last directory only. And then (.*\\?.*) gathers the rest of that line into group 3.
Finally (\n.*\2.*)* checks to see if there is a backreference to group 2, \2, in any following lines. [Technically, that backreference could be anywhere in a line, even the beginning, that can be fixed if necessary - let me know if you need that for your data. See safer regex101 demo if 'someText-/d.' could appear anywhere and should be ignored if not last directory and use that find.]
You can not use a single capture group for the whole line using ^(.*), as you want to repeat only the part before the last dot using a backreference and that will not work capturing the whole line.
Therefore you have to capture the digits in the first match in a separate capture group to keep it in the replacement.
If you want to match all following lines with the same text before the last dot, you can use a repeating group:
^\s*(.*\.)(\d+\\[^\\\r\n]*)(?:\r?\n\s*\1\d*\\[^\\\r\n]*)+
The pattern matches:
^ Start of string
\s* Match optional whitespace chars
(.*\.) Capture group 1, match till the last dot
(\d+\\[^\\\r\n]*) Capture group 2, match 1+ digits, \ and optional chars other than \ or a newline
(?: Non capture group
\r?\n\s*\1 Match a newline and a backreference to group 1
\d+\\[^\\\r\n]* Same pattern as in the first part
)+ Close the non capture group and repeat 1+ times
See a regex demo.
In the replacement use the 2 capture groups $1$2
The replacement will look like
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder\Artifact\Artifact-1.0\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder2\Artifact\Artifact-1.1\data.xxx
D:\Folder1\Folder2\Folder3\Folder4\Folder5\ArtifactFolder3\Artifact\Artifact-1.2\data.xxx

Check if a phrase occurs at least N times in a document using only REGEX

so I have, for example, this toy text/code:
db.session.commit()
#rollback
user = User.query.get(1)
print("Some printouts")
db.session.rollback()
print("\nCommit the change:")
db.session.commit()
I want to write a JavaScript based regular expression to check if db.session.commit() is written at least, for example, 2 times. If it's written fewer than 2 times, the match should fail (I would use this for testing the code). This should work for any N times (2, 3, ...).
I tried gazillion of combinations on Regex101, with {} repetition and (non)capturing groups but nothing worked well so far. I am still learning. I managed to get this far: https://regex101.com/r/V4RPcb/4 but the moment you put something in the empty space between two commits (or anywhere else), it fails. I haven't found similar questions anywhere else (which could also mean it is trivial lol). I know I could use a for loop, but in this case, I can only use regex.
Any help is very much appreciated.
You can match the whole line and capture it in group 1. Then match a newline followed by 0+ whitespace chars and use a backreference \1 to match the same again.
^(db\.session\.commit)\r?\n\s*\1
regex demo
If you want to match it 2 times for the example data when there can also be other lines in between:
^(db\.session\.commit\(\))(?:\r?\n(?!\1).*)*\r?\n\1
In parts
^ Start of line
( Capture group 1
db\.session\.commit\(\) Match db.session.commit()
) Close group
(?: Non capture group
\r?\n Match a newline
(?!\1).* Assert what is directly to the right is not what is captured in group 1 using a back reference. If that is the case, match the whole line.
)* Close group and repeat 0+ times
\r?\n Match a newline
\1 Backreference to what is captured in group 1
Regex demo
Try:
(db\.session\.commit\(\))[\s\S]*?\1
(db\.session\.commit\(\)) # matches: db.session.commit() in capture group 1
[\s\S]*? # matches any character including newline non-greedily 0 or more times
\1 # matches capture group 1, i.e. db.session.commit()
The above should find any two occurrences of db.session.commit() anywhere in the string, whether on the same line or not.
Regex Demo

Regex for text file

I have a text file with the following text:
andal-4.1.0.jar
besc_2.1.0-beta
prov-3.0.jar
add4lib-1.0.jar
com_lab_2.0.jar
astrix
lis-2_0_1.jar
Is there any way i can split the name and the version using regex. I want to use the results to make two columns 'Name' and 'Version' in excel.
So i want the results from regex to look like
andal 4.1.0.jar
besc 2.1.0-beta
prov 3.0.jar
add4lib 1.0.jar
com_lab 2.0.jar
astrix
lis 2_0_1.jar
So far I have used ^(?:.*-(?=\d)|\D+) to get the Version and -\d.*$ to get the Name separately. The problem with this is that when i do it for a large text file, the results from the two regex are not in the same order. So is there any way to get the results in the way I have mentioned above?
Ctrl+H
Find what: ^(.+?)[-_](\d.*)$
Replace with: $1\t$2
check Wrap around
check Regular expression
UNCHECK . matches newline
Replace all
Explanation:
^ # beginning of line
(.+?) # group 1, 1 or more any character but newline, not greedy
[-_] # a dash or underscore
(\d.*) # group 2, a digit then 0 or more any character but newline
$ # end of line
Replacement:
$1 # content of group 1
\t # a tabulation, you may replace with what you want
$2 # content of group 2
Result for given example:
andal 4.1.0.jar
besc 2.1.0-beta
prov 3.0.jar
add4lib 1.0.jar
com_lab 2.0.jar
astrix
lis 2_0_1.jar
Not quite sure what you meant for the problem in large file, and I believe the two regex you showed are doing opposite as what you said: first one should get you the name and second one should give you version.
Anyway, here is the assumption I have to guess what may make sense to you:
"Name" may follow by - or _, followed by version string.
"Version" string is something preceded by - or _, with some digit, followed by a dot or underscore, followed by some digit, and then any string.
If these assumption make sense, you may use
^(.+?)(?:[-_](\d+[._]\d+.*))?$
as your regex. Group 1 is will be the name, Group 2 will be the Version.
Demo in regex101: https://regex101.com/r/RnwMaw/3
Explanation of regex
^ start of line
(.+?) "Name" part, using reluctant match of
at least 1 character
(?: )? Optional group of "Version String", which
consists of:
[-_] - or _
( ) Followed by the "Version" , which is
\d+ at least 1 digit,
[._] then 1 dot or underscore,
\d+ then at least 1 digit,
.* then any string
$ end of line

Regular Expression to Extract Text Bounded by '/'

I need to a regular expression to extract names from a GEDCOM file. The format is:
Fred Joseph /Smith/
Where the text bounded by the / is the surname and the Fred Joseph are the forenames. The complication is that the surname could be at any place in the text or may not be there at all. I need something that will extract the surname and capture everything else as the forenames.
This is as far as I have got and I have tried making groups optional with the ? qualifier but to no avail:
As you can see it has several problems: If the surname is missing nothing gets captured, the forename(s) sometimes have leading and trailing spaces, and I have 3 capture groups when I'd really like 2. Even better would be if the capture group for the surname didn't include the '/' characters.
Any help would be much appreciated.
For your last line, I'm not sure there is a way to join the group 1 with group 3 into a single group.
Here is my proposed solution. It doesn't capture spaces around forenames.
^(?:\h*([a-z\h]+\b)\h*)?(?:\/([a-z\h]+)\/)?(?:\h*([a-z\h]+\b)\h*)?$
To correctly match the names, care to use the insensitive flag, and if you test all lines at once, use multiline flag.
See the demo
Explanation
^ start of the line
(?:\h*([a-z\h]+\b)\h*)? first non-capturing group that matches 0 or 1 time:
\h* 0 or more horizontal spaces
([a-z\h]+\b) captures in a group letters and spaces, but stops at the end of the last word
\h* matches the possible remaining spaces without capturing
(?:\/([a-z\h]+)\/)? second non-capturing group that matches 0 or 1 time a name in a capturing group surrounded by slashes
(?:\h*([a-z\h]+\b)\h*)? third non-capturing group doing the same as first one, capturing the names in a third group.
$ end of the line
For your requirements
([A-z a-z /])+\w*
Sample
Hope this helps
(.\*?)\\/(.\*?)\\/(.\*)
Try this: ^([^/]*)(/[^/]+/)?([^/]*)$
This matches the following:
^ start of string (or with multiline modifier start of line)
([^/\n]*) anything other than / or new line zero or more times - this is captured as group 1
(/[^/\n]+/)? a single / followed by one or more non / or new line characters, then a single '/' character - this is captured as group 2, and is optional
([^/\n]*) anything other than / or new line zero or more times - this is captured as group 3
$ end of string (or with multiline modifier end of line)
You can see in action with your example text here: https://regex101.com/r/9kmKpy/1
To not capture the slashes you can add a non capturing group by adding ?: to the second set of brackets, and then adding another pair between the slashes:
^([^\/\n]*)(?:\/([^\/\n]+)\/)?([^\/\n]*)$
https://regex101.com/r/9kmKpy/2
I am not sure I follow what language is being used to extract the data, but based on what you have so far, you simply need to add '?':
(.*)(\/?.*\/?)(.*)
Not that this does not give you groupings for EACH name as some solutions will have multiple names in a single group
Edit:
Extending on Niitaku solution and looking at having each individual name in its own group, you could use:
^\s*(?:\/?([a-z]+)\/?)\s*(?:\/?([a-z]+)\/?)\s*(?:\/?([a-z]+)\/?)\s*$
As explained though, if using a language like ruby it would simply be:
ruby -pe '$_ = $_.scan(/\w+/)' file

Regex with optional, lazy, greedy group

Let's take this source string from a word document:
A;SDLFJA;SDJFA;KSDJF;ALKSJDF SOURCE: 3 55 ASDKLFJA;KDSJF
sa;ldkjfa SOURCE: HYPERLINK "ASDLFA;SDFA;SKD" "MATCH9" 3 HYPERLINK
"ASDLFA;SDFA;SKD" "MATCH10" 55 a;sdkfja;ksdfj;aklsdjf;lk
I'm looking for a pattern that is composed of the literal text "SOURCE: " followed by a 1 digit number a space and a 2 digit number.
For example, in the first line of the source string, I want to find "SOURCE: 3 55".
Now, some clever boffin has decided to embed a hyperlink for the 1 digit number and another hyperlink for the 2 digit number. Lines 2 and 3 show the two embedded hyperlinks. MATCH1 refers to the first embedded hyperlink, MATCH2 is the second, and so on. I have no way of knowing how many hyperlinks will be placed before these, so one can't assume MATCH9 and MATCH10.
The text I want to extract is the "3 55" portion. I want to put it into a named group I'll call "KeepMe".
I don't mind using two different patterns, one for the hyperlink and one without.
Here's a pattern that works for the non-hyperlinked text:
SOURCE:\s+(?<KeepMe>\d*\s+\d*)
I get "3 55" in the KeepMe group just like I want.
I haven't been able to keep the hyperlink match pattern from being greedy.
Here's a failed regex pattern, (one of many):
SOURCE:\s+(?<Hyperlink>HYPERLINK.*MATCH\d*\u0022\s+)??(?<KeepMe1>\d*)\s+
(?<Hyperlink>HYPERLINK.*MATCH\d*\u0022\s+)??(?<KeepMe2>\d*)
In the above pattern, I'm trying to say:
Look for the literal SOURCE: followed by one or more spaces.
Then, optionally look for the literal text "HYPERLINK followed by some characters, followed by the literal text MATCH, followed by some digits and a double quote character in a lazy, non-greedy manner, followed by one or more spaces, followed by some digits I want to keep. Then, do another HYPERLINK pattern match like we just did and keep the digits after that, too.
Remember, in both cases, I want to extract "3 55". It can be extracted in one or two pieces though one would be best.
Any ideas???
This should do the trick:
\bSOURCE:\s+(?:HYPERLINK\s+"[^"]*"\s+"MATCH\d+"\s+)?(?<KeepMe1>\d+)\s+(?:HYPERLINK\s+"[^"]*"\s+"MATCH\d+"\s+)?(?<KeepMe2>\d+)\b
Main difference is that I replaced the .* between HYPERLINK and MATCH with something less greedy.
Fiddle: https://regex101.com/r/yE3fP4/1
A Regex that works for just the hyperlinked case is:
/(?<SourceToken>SOURCE:) # Start with a source tag
\s+ # Followed by whitespace
(?<HyperlinkMatchGroup> # Save the hyperlink & match combo.
(?<Hyperlink> # Save the hyperlink (to be discarded)
(?<HyperlinkToken>HYPERLINK\s+) # Hyperlinks start with the literal tag "HYPERLINK"
(?<HyperlinkText>".*?") # Hyperlink text contained in quotes, non-greedy
\s*) # Followed by whitespace
* # Repeating any number of times
(?<MatchToken>"MATCH\d*") # Followed by a literal tag "MATCH" and a digit string
\s* # Followed by whitespace
(?<KeepMe>\d+) # Finally, the match, which is just a series of digits
\s* # Followed by whitespace
)+ # The whole hyperlink & match pair must occur at least once
/x
It may or may not cover all your cases; I haven't spent much time digging into it.