Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Does anyone know why ls | xargs -n 1 basename | grep -E '[0-9]{1}' does not only show occurrences of a single digit between 0-9?

For example if I have:

touch 1
touch 22
touch 333
touch test_file

If I run ls | xargs -n 1 basename | grep -P '[0-9]{1}':

I get 1, 22 and 333 files listed.

If I run ls | xargs -n 1 basename | grep -P '[0-9]{2}':

I get 22 and 333 files listed.

I would expect the {2} to only show the 22 file.

share|improve this question

4 Answers 4

up vote 1 down vote accepted

I think the reason you are expecting something different than what is happening is that you are taking

[0-9]{n}

to mean match only exactly n occurrences of characters in [0-9] and throw the rest away, when what its doing is finding n occurrences of characters in [0-9] and calling it a match. So in

[0-9]{1}

it finds any exactly one occurrence of something matching [0-9] so 1 matches, then when examining the 22, it stops at the first 2 and considers it a match without looking at the rest. Similarly for the 333. If you specified

[0-9]{2}

it would not match 1, but 22 matches, and then the 33 in 333 matches so it grabs it.

As slm said, word boundaries are the ticket here. You have both the -P and the -E flavors of grep regex matching in your question. You can use the GNU \< and \> with -E like this:

ls | xargs -n 1 basename | grep -E '\<[0-9]{1}\>'

or the \b with the either -E or the pcre matching -P like this

ls | xargs -n 1 basename | grep -P '\b[0-9]{1}\b'
share|improve this answer

Use grep -x to find exact matches instead of finding matches inside lines.

grep foo finds lines that contain foo, while grep -x foo finds line that are foo.

Same thing, grep -E '[0-9]{1}' (same as grep '[0-9]') finds lines that contain one digit. grep -xE '[0-9]{1}' finds lines that consist of one digit.

With GNU grep, you can use the --color or -o options to see what is being matched though it will show you all the matches.

share|improve this answer

If you put the contents of your examples in a text file you can see why grep is matching it:

$ grep -E '[0-9]{1}' file.txt
touch 1
touch 22
touch 333

You can use word boundaries (-w) to tell grep that you want to only match a single word of your regex (\<REGEX\>) rather than any string that happens to match it:

$ grep -Ew '\<[0-9]{1}\>' file.txt
touch 1

$ grep -Ew '\<[0-9]{2}\>' file.txt
touch 22

$ grep -Ew '\<[0-9]{3}\>' file.txt
touch 333

The \< and \> tell grep where the boundaries are. This forces grep to match explicit words such as 1 but not strings such as 11, 111, or aa11aa, since the regex within these strings isn't contained by word boundaries.

share|improve this answer

This is because the first two chars of 333 match the regex, so the whole line matches.

If you want to avoid that you could use:

(^|[^0-9])[0-9]{2}([^0-9]|$)
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.