5

According to the following tutorials

  1. https://linuxize.com/post/regular-expressions-in-grep/

\s Match a space.

and

  1. https://www.guru99.com/linux-regular-expressions.html

Some interval regular expressions are:

Expression Description

{n} Matches the preceding character appearing 'n' times exactly

{n,m} Matches the preceding character appearing 'n' times but not more than m

{n, } Matches the preceding character only when it appears 'n' times or more

Sample file

wolf@linux:~$ cat space.txt
0space
1 spaces
2  spaces
3   spaces
4    spaces
wolf@linux:~$ 

I just want to grep up to 3 spaces only, minimum 1 space, maximum 3 spaces Unfortunately, it doesn't really work as expected.

wolf@linux:~$ cat space.txt | grep -P '\s{1,3}'
1 spaces
2  spaces
3   spaces
4    spaces
wolf@linux:~$ 

wolf@linux:~$ cat space.txt | grep -P '\s{3}'
3   spaces
4    spaces
wolf@linux:~$ 

wolf@linux:~$ cat space.txt | grep -P '\s{3,3}'
3   spaces
4    spaces
wolf@linux:~$ 

wolf@linux:~$ cat space.txt | grep -P '\s{0,3}'
0space
1 spaces
2  spaces
3   spaces
4    spaces
wolf@linux:~$ 

Desired Output

wolf@linux:~$ cat space.txt | grep -P '\s{0,3}' <- need to fix it here
1 spaces
2  spaces
3   spaces
wolf@linux:~$ 
3
  • 1
    Just a brief note regarding your problem. If you think of this with the letter 'a', you're trying to catch 'a', 'aa' & 'aaa'. But these patterns are all contained within the string 'aaaa', which is why it turns up in your results. You need to anchor the pattern in an appropriate way to get around that.
    – Haxiel
    Commented Oct 9, 2020 at 5:37
  • 1
    Thanks @Haxiel. I got it now. That's because PCRE grep is different from GNU grep.
    – Wolf
    Commented Oct 9, 2020 at 6:16
  • 1
    @Wolf no, it has nothing to do with PCRE. It's the same in any regex flavor, which is why echo aaaa | grep 'a' works. Or, with extended (not PCRE) regular expressions and a specific number: echo aaaa | grep -E 'a{1,2}'. The braces mean "I need to find this many characters". Whether or not there are also more after or before the braces is irrelevant.
    – terdon
    Commented Oct 10, 2020 at 12:57

4 Answers 4

10

you need:

grep -P '\S\s{1,3}\S' infile

\s matches a whitespace-character, not only a space.
\S matches a non-whitespace-character

in your attempt, you are not limiting that before &after your matches should not be a whitespace.


to filter on space only and avoid using PCRE, you can do:

grep '[^ ] \{1,3\}[^ ]' infile

or to work on lines having leading/trailing 1~3spaces:

grep '\([^ ]\|^\) \{1,3\}\([^ ]\|$\)' infile

from https://regexper.com/

input data (cat -e infile):

0space$
1 spaces$
2  spaces$
3   spaces$
4    spaces$
   3spaces$
    4space$
3spaces   $
4spaces    $

output:

1 spaces$
2  spaces$
3   spaces$
   3spaces$
3spaces   $
5
  • 3
    Your \S\s{1,3}\S will not match three spaces at the beginning or the end of the line.
    – user313992
    Commented Oct 9, 2020 at 5:35
  • 1
    @UncleBilly please continue reading the whole answer, I should not resolve everything to OP, there is hint that s/he can get help : ) Commented Oct 9, 2020 at 5:37
  • I still don't get it: if you're using -P (pcre), why not (?<!\s)\s{1,3}(?!\s)?
    – user313992
    Commented Oct 9, 2020 at 5:41
  • 2
    Thanks @αғsнιη, I've got it. The problem was due to non-whitespace-character which is the numbers before the whitespace isn't it?
    – Wolf
    Commented Oct 9, 2020 at 6:03
  • 2
    @Wolf no, the problem is "you are not limiting that before &after your matches (minimum 1 and maximum 3 space here) should not be a whitespace" and your grep matches line 4\ \ \ \ spaces as well, since it matches condition "line with at least 1 and at most 3 whitespaces" Commented Oct 9, 2020 at 6:41
10

If you want to match on a sequence of 1 to 3 whitespace characters not surrounded by whitespaces, that's where you'd use Perl look-around operators:

grep -P '(?<!\s)\s{1,3}(?!\s)'

It matches on:

         1
1234567890123456789
    a b  c   d    e
     ^ ^^ ^^^ 

With standard grep, you could achieve the same effect with:

grep -E '(^|[^[:space:]])[[:space:]]{1,3}([^[:space:]]|$)'

This time we match on the sequence of 1 to 3 whitespace characters and the non-whitespace on either side (or the start (^) or end ($) of the subject).

         1
1234567890123456789
   a b  c   d    e
^^^^ ^^^^

(with -o (a GNU extension), you'd find it doesn't report a b as a was already matched earlier; when searching for more matches, it starts at the next character after the last match).

Without -E, you get basic regexps that don't have alternation operators (though some grep implementations support \| for that as an extension), but standardly, you could still do:

grep -x '\(.*[^[:space:]]\)\{0,1\}[[:space:]]\{1,3\}\([^[:space:]].*\)\{0,1\}'

This time, the regexp matches the whole line including the 1 to 3 spaces and an optional (\{0,1\} the equivalent of ERE ?) leading part to it ending in a non-whitespace and an optional part following it that starts with a non-whitespace.

         1
1234567890123456789
   a b  c   d    e
^^^^^^^^^^^^^^^^^^

In any case, those would still return lines that contain a sequence of 4 or more whitespaces as long as they also contain a sequence of 1 to 3 whitespace not surrounded by whitespaces.

If the point was to exclude lines containing sequences of 4 or more whitespaces then it would just be:

grep -vE '[[:space:]]{4}'

Or if you still require at least one whitespace, or in other words that the line contains one or more sequences of whitespace characters all of which have at least one whitespace but no more than 3:

grep -vE -e '[[:space:]]{4}' -e '^[^[:space:]]*$'

That is return all the lines except the ones that contain a sequence of 4 whitespace and the ones that are made exclusively of non-whitespaces.

Or again with Perl's look around operators:

grep -P '^(?=.*\s)(?!.*\s{4})'

That is match the beginning of the line, provided that it's followed by any amount of characters and a whitespace and that it's not followed by any amount of characters and a sequence of 4 whitespace.

Though it would be more legible with sed or awk where you can do both a positive and negative match in the same invocation:

awk '/[[:space:]]/ && ! /[[:space:]]{4}/'
sed '/[[:space:]]/!d; /[[:space:]]\{4\}/d'
6

You can come up from the opposite. Exclude lines with more than 3 spaces in the substring.

grep -Ev '\s{4,}'

-v Invert the sense of matching, to select non-matching lines.
You can insert anchors as non-whitespace characters

grep -E '\S\s{1,3}\S'
3
  • 2
    Thanks @nezabudka, that's correct. However, I've just realized that my desired output was wrong. I've updated it. Any idea how to remove the 1st line using -v ?
    – Wolf
    Commented Oct 9, 2020 at 6:07
  • 1
    If I'm not wrong, \S will need to match a character, which means that the second expression won't return lines where the spaces are located at the extreme start or end of the line.
    – Kusalananda
    Commented Oct 9, 2020 at 6:51
  • 1
    @Kuslananda, Thanks. Of course, only for the specific case from the given example. Otherwise it should look like this: grep -E '(^|\S)\s{1,3}(\S|$)'
    – nezabudka
    Commented Oct 9, 2020 at 17:09
-1
$ grep -E '[[:space:]]' < file |
  grep -vE '[[:space:]]{4}'
1 spaces
2  spaces
3   spaces
  • first filter all lines with atleast 1 space char.
  • from these, filter out all lines with 4 or more space characters.
  • what remains is lines comprising 1 to 3 space characters.
2
  • 1
    Like in @Kusalananda's now deleted answer, that does however not report lines that contain both sequences of 1 to 3 whitespace and sequences of more than 4 whitespace. Commented Oct 9, 2020 at 13:39
  • But is that not a contradiction? OTOH we want (1,3) spaces but don't want more than 4. Such cases needed to be worded unambiguously and not be left as a responsibility of the responders. At least the testcase shud show such at least. Commented Oct 9, 2020 at 13:59

You must log in to answer this question.

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