1

I am new to GNU/Linux and regex. Recently I've been playing around trying to get to grips with regex. So far I feel I've got a pretty solid foundational understanding. I'm digging PCRE at the moment.

This is the practice text file I'm playing about with:

01234 567890

01111-222111

09876.543210

I can successfully match the numbers by doing something like this:

(\d{5})[-.]?\s*?(\d{6})

Now I wanted to create a non-capturing group in order to miss out the first 5 digits and only match the last 6. So I guess I throw in (?:) to represent the non-capturing group followed by whatever I want to not be captured, right? So that would be

(?:\d{5})[-.]?\s*?(\d{6})

I do that and in my terminal using grep -Po for PCRE and show output I'm still getting a full match as if the non-capturing group did not apply.

Any guidance?

0

4 Answers 4

2

Capturing doesn't effect what grep considers to be the matched parts when using the -o or --only-matching option. All non-capturing means is that you don't intend to count the group as one of the available backreferences (or replacements, in contexts where replacement is an option).

For example:

$ printf 'aba\nabb\nabc\n' | grep -Po '(a)(b)'
ab
ab
ab
$ printf 'aba\nabb\nabc\n' | grep -Po '(a)(b)\1'
aba
$ printf 'aba\nabb\nabc\n' | grep -Po '(?:a)(b)\1'
abb

Probably what you are looking for in this context is either a zero length lookbehind assertion:

printf 'aba\nabb\nabc\n' | grep -Po '(?<=a)b'
b
b
b

or the \K "keep left" assertion

$ printf 'aba\nabb\nabc\n' | grep -Po 'a\Kb'
b
b
b

(the latter is slightly more flexible since it allows variable length matching).

So for example

$ grep -Po '\d{5}[-.]?\s*\K\d{6}' file
567890
222111
543210
4
  • I was beginning to see that myself. It does make sense because grep is used to find patterns in text, and not to be responsible for what comes after that. Am I correct in assuming this? It matches the regex regardless of output while the pattern has not been piped to another command. After that you can utilise the matching groups to replace, substitute, insert, append etc with something like sed? I'm learning about sed at the moment and finding it is using a slightly different syntax. So juggling these variations is tricky atm! Learning about lookarounds also. Thanks for your input
    – customcup
    May 12, 2020 at 13:52
  • In perl (PCRE really) a *? means "lazy matching", i.e.: use the shortest string needed to match the whole regex (as opposed to a greedy *). @steeldriver
    – user232326
    May 12, 2020 at 14:20
  • @Isaac doh! of course, don't know how I missed that one ... May 12, 2020 at 14:24
  • @steeldriver Having to use BRE, ERE and PCRE (just to name three) Is a botch. Not naming the variations that javascript, PHP, Python, and golang introduce (just some of them).
    – user232326
    May 12, 2020 at 14:56
0

The regex you are using seems too complex to only match one charater in the middle, either a -, a . or an space. Why do you need [-.]?\s*?. That reads: Match a - or a . (optionally (`?)) followed by space (well, really (from man pcrepattern): The default \s characters are now HT (9), LF (10), VT (11), FF (12), CR (13), and space (32)). Well, actually, several spaces in lazy mode (*?).

In my opinion, all it needs to be is [ .-] simple, one character, either an space, a dot, or a dash. This regex:

(\d{5})[-. ](\d{6})

The best place to try (the only real way to learn regexes) is to go to regex101.com and try. Here I created an example of this post in detail (in pcre, yes).

You can see that in the replacement, what you are asking about, I placed this replacement:

(one:\1) (two:\2) (three:3)

And you can see that on each line the whole match, from the start of the regex to its end, but not the surrounding text, gets replaced with that string where \1 and \2 are converted to the captured values. One for each (...).

If you want to make the first (...) non capturing, then there will be only one capturing group, and the replacement should change to:

(one:1) (two:\1) (three:3)

Only one \1. Or else, the replacement will fail.

If you want to replace the whole line, make it match the whole line, from start to end:

^.*(?:\d{5})[-. ](\d{6}).*$

And make the replacement only \1 to print the last group of digits.

Now, about grep. Grep doesn't have replacements, there is something that "kind of" helps, but is not a good equivalent: \K.

grep -Po '^.*\d{5}[-. ]\K\d{6}' file

One important idea to get is that -o is intended to give out all that the regex matched, yes, the whole regex, not each matching parenthesis.

To work with a real replacement (or substitute (s///)) you need sed (but it uses BRE not PCRE):

$ sed 's/^.*\([0-9]\{5\}\)[-. ]\([0-9]\{6\}\).*$/ \2 \1 /' file
 567890 01234 
 222111 01111 
 543210 09876

that does a real replacement and it allows to change the order (or to repeat).

1
  • Hi, thanks for your in-depth reply. Appreciate the effort you made to explain things to me. It really does help. I'm just playing around with different regex at the minute and I agree I'm finding I'm using complex regex when it could be far more simpler. Just par for the course I guess when you're starting out. I'm working with sed at the moment and learning about it and a course I completed used sed quite a bit in editing text. Thanks again. Will come back to this thread I started as it's really helped
    – customcup
    May 12, 2020 at 13:59
0

If you only want to grep out the chunks of lines where the last 6 characters are digits then just say that

grep -Po "\d{6}$" file

Add the look behind if you want to be sure of a delimiter

grep -Po "(?<=[-. ])\d{6}$" file

Or if the number of digits is indeterminate

grep -Po "\d+$" file

Just anchor to the end of the line and match back in both cases.

I find the best plan is Usually not to deal with the bits you don't need to deal with (though there's every virtue in doing things the hard way in order to learn....keep it up ;D).

0

You can get the output you want by using the pcregrep command on Linux.  It extends the -o option to -onumber, which lets you output selected capture group(s).  Since you want the second group, you would use -o2:

$ pcregrep -o2 '(\d{5})[-.]?\s*?(\d{6})' input
567890
222111
543210

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .