how to match only once occurrence of a double space of a line?

Viewed 276

line A

foo bar  bar foo  bar foo

line B

foo bar  bar foo

In line A, there are multiple occurrence of double space.

I only want to match lines like line B which has only once double space occurrence.

I tried

^.*\s{2}.*$

but it will match both.

How may I have the desired output? Thank you.

3 Answers

You can do:

^(?!.*[ \t]{2,}.*[ \t]{2,})

# Negative look ahead assertion that states 'only start the match 
# on this line IF there are NOT 2 (or potentially more) breaks with
# two (or potentially more) of tabs or spaces'.

Demo 1

If you want to require ONE double space in the line but not more:

^(?=.*[ \t]{2,})(?!.*[ \t]{2,}.*[ \t]{2,})

# Positive look ahead that states 'only start this match if there is 
# at least one break with two tabs or spaces'

# BUT

# Negative look ahead assertion that states 'only start the match 
# on this line IF there are NOT 2 (or potentially more) breaks with
# two (or potentially more) of tabs or spaces'.

Demo 2

If you want to limit to only two spaces (not tabs and not more than 2 spaces):

^(?=.*[ ]{2})(?!.*[ ]{2}.*[ ]{2})
# Same as above but remove the tabs as part of the assertion

Demo 3

Note: In your regex you have \s as the class for a space. That also matches [\r\n\t\f\v ] so both horizontal and vertical space characters.

Note 2: You can do this without a regex as well (assuming you only want lines that have 1 and only 1 double space in them):

txt='''\
line A

foo bar  bar foo  bar foo
line B

foo bar  bar foo'''

>>> [line for line in txt.splitlines() if len(line.split('  '))==2]
['foo bar  bar foo']

If you wish to match strings that contain no more than one string of two or more spaces between words you could use following regular expression.

r'^(?!(?:.*(?<! ) {2,}(?! )){2})'

Start your engine!

Note that this expression matches

abc    de fgh

where there are four spaces between 'c' and 'd'.

Python's regex engine performs the following operations.

^
(?!           : begin negative lookahead
  (?:         : begin non-capture group
    .*        : match 0+ characters other than line terminators
    (?<!      : begin negative lookbehind
      [ ]{2,} : match 2+ spaces
      (?! )   : negative lookahead asserts match is not followed by a space
    )         : end negative lookbehind
  )           : end non-capture group
  {2}         : execute non-capture group twice
)             : end negative lookahead

You can get the match without lookarounds by starting the match with 1+ non whitespace chars.

Then optionally repeat a single whitespace char followed by non whitespace chars before and after matching a double whitespace char.

The negated character class [^\S\r\n] will match any whitespace chars except a newline or carriage return. If you want to allow matching newlines as well, you could use \s

^\S+(?:[^\S\r\n]\S+)*[^\S\r\n]{2}(?:\S+[^\S\r\n])*\S+$

Explanation

  • ^ Start of string
  • \S+ Match 1+ non whitespace chars
  • (?: Non capture group
    • [^\S\r\n]\S+ Match a whitespace char without a newline
  • )* Close group and repeat 0+ times
  • [^\S\r\n]{2} Match the 2 whitespace chars without a newline
  • (?: Non capture group
    • \S+[^\S\r\n] Match 1+ non whitespace chars followed by a whitespace char without a newline
  • )* Close group a and repeat 1+ times
  • \S+ Match 1+ non whitespace chars
  • $ End of string

Regex demo

Related