grep regex - how do I match same character pairs?

Viewed 160

Say I have the following string:

blah blah blah \the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\ blah blah blah \ \\ \\ \\ \ foobar \ a\\b\\c\\ \

and I want to match the following 3 matches using grep:

\the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\

and

\ \\ \\ \\ \

and

\ a\\b\\c\\ \

to do this I need a way to pair '\\' as to only end the match when there is a single closing '\' that isn't part of a pair.

so far I have this:

echo $string | grep -oP '\\((?!\\).)*\\'

edit: I managed to get it working in the regex101 environment:

\\((?!\\).|(([\\]{2})+))+\\

https://regex101.com/r/wC2cF1/13

but it's still giving me the same result in grep perl

5 Answers

Use

text='blah blah blah \the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\ blah blah blah \ \\ \\ \\ \ foobar \ a\\b\\c\\ \'
echo "$text" | grep -oE '\\([^\\]|\\\\)+\\'

Output:

\the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\
\ \\ \\ \\ \
\ a\\b\\c\\ \

If you have GNU grep then @RyszardCzech's answer is a good solution, otherwise using any awk in any shell on every UNIX box:

$ cat tst.awk
{
    gsub(/\\\\/,RS)
    while ( match($0,/\\[^\\]*\\/) ) {
        tgt = substr($0,RSTART,RLENGTH)
        gsub(RS,"\\\\",tgt)
        print tgt
        $0 = substr($0,RSTART+RLENGTH)
    }
}

.

$ awk -f tst.awk file
\the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\
\ \\ \\ \\ \
\ a\\b\\c\\ \

Using the core Text::Balanced module to extract the string:

$ perl -MText::Balanced=extract_delimited -nE '$text = extract_delimited($_, q/\\/, qr/^[^\\]*/, q/\\/); say $text' input.txt
\the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\

Note: This solution is simpler and better than the answer below. But beware that its behaviour is different on a string \\\xy\, for example.


Using GNU utilities:
sed 's/\\\\/\x00/g' file | grep -ao '\\[^\\]*\\' | sed 's/\x00/\\\\/g'

  • The first sed replaces each double backslash (\\) with a null character (highly unlikely to occur in the original data to be processed).
  • The grep captures and prints the characters between matching single backslashes (\). The GNU specific -a option allows to process a binary file as if it were a text file since the stream may contain null characters at this point. With the GNU specific -o option, grep prints only the matching parts of the line, each one on a separate output line.
  • The last sed restores the double backslashes by replacing each null character with a \\.

Please notice that those are highly GNU specific.


Test:

$ line='blah blah blah \the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\ blah blah blah \ \\ \\ \\ \ foobar \ a\\b\\c\\ \'
$ sed 's/\\\\/\x00/g' <<< "$line" | grep -ao '\\[^\\]*\\' | sed 's/\x00/\\\\/g'
\the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\
\ \\ \\ \\ \
\ a\\b\\c\\ \

With echo grep and tail...

string='blah blah blah \the rain in sp\\\\ain moves mainly\\ on the p\\lain\\\\\ blah blah blah \ \\ \\ \\ \ foobar \ a\\b\\c\\ \'

echo ${string} | grep -o -E "([ \]{1,2}[ a-z]{0,2}[ \]{0,2}){1,4}" | tail -n2 | grep -o -E "[abc \]{1,32}"

Puts out...

 \ \\ \\ \\ \ 
 \ a\\b\\c\\ \

grep -E means: Using an extended regular expression

Related