After executing

grep '[a-z]\{22\}' /usr/share/dict/words

the output is


but while executing

grep '[^a-z]\{22\}' /usr/share/dict/words

there is not output.

I was expecting that it will generate similar results as that of

grep -v '[a-z]\{22\}' /usr/share/dict/words

My question is what's wrong and why ?

  • It might help an Answerer if you described your current understanding of [^a-z] and grep -v.
    – Jeff Schaller
    May 24, 2018 at 13:12
  • @JeffSchaller [^a-z] not to match any one in the range and grep -v means that it will show non-matching lines. May 24, 2018 at 13:15

2 Answers 2


[specification] matches a collating element (can be a character or sequence of characters as defined in the collating algorithm for the locale (for instance, in Hungarian locales on GNU systems, dzs is a collating element that sorts somewhere between d and e)) in the specified set.

That specification can include

  • ranges like a-z (or [.dzs.]-z) for collating elements that collate between a and z (note that it generally includes abcdefghijklmnoprstuvwxyz but in most locales, it includes a lot more). Also, as POSIX leaves it unspecified for locales other than the POSIX locales, how much those ranges are based on the collation order varies significantly between implementations.
  • individual characters or collating elements (x, [.dsz.])
  • POSIX character classes [:alpha:], [:digit:]
  • equivalent classes like [=e=] for all the collating elements that have the same primary collating weight as e (could include things like é)

So, for instance, [acd[=e=]h-k[:digit:][.dzs.]] matches on a collating element provided it's either a, c, d, dzs or is equivalent to e or collates between h and k or is classified as digit.

And if the specification starts with ^, then it still matches one collating element, but with the set complemented. That is any collating element but the ones specified.

So [^a-z] matches on any collating element that does not collate between a and z. For instance, it would probably match on 1 and , possibly on X or DSZ depending on the locale and the grep implementation, but not on a, x nor z and probably not on é.

So grep '[^a-z]\{22\}' matches on lines that contain a sequence of 22 collating elements that collate either before a or after z.

While grep -v '[a-z]\{22\}' matches on lines that do not contain a sequence of 22 collating elements collating between a and z.

Matching the same without -v is almost impossible to implement, you'd need to match on lines that contain no more than 21 [a-z] collating elements in between two [^a-z] element. But if the locale supports multi-character collating elements, that's not really possible. For instance, in those Hungarian locales, [a-z] matches on dsz but also on d, s, and z so you'll find that there, [a-z]{0,21} will match on dszxxxyyyxxxyyyxxxyyyx but also would [a-z]{22}.

For locales that don't have multi-character collating elements, you can do something like:

grep  '^[^a-z]*\([a-z]\{1,21\}[^a-z]\{1,\}\)*[a-z]\{0,21\}$'

Now, there are also some grep implementations that support more advanced regular syntaxes with options that have some negation operator.

For instance, the GNU or ast-open implementation of grep support perl-like (using libpcre in GNU grep, ast-open's own implementation for ast-open grep) regular expressions with the -P option which has a (?!pattern) negative look-ahead operator.

(?!pattern) matches with zero width at any point on the subject string provided the pattern doesn't match starting from there. So one could use:

 grep -P '^(?!.*[a-z]{22})'

to match on the start of the line provided it's not followed by any number of characters and 22 [a-z]s. Note however that in PCRE (not in ast-open), [a-z] only matches on abcdefghijklmnopqrstuvwxyz regardless of the locale.

ast-open also takes a -X option for what they call augmented regexps. Those augmented regex have a ! operator that negates things. x! would match on anything other than x (including the empty string).

So with ast-open grep, you could also do:

grep -X '^(.*[a-z]{22}.*)!$'
  • Can you please shed some light for the use of ! in RE which you earlier mentioned about. May 24, 2018 at 16:47
  • @Neo_Returns, see edit. May 24, 2018 at 19:08
  • Tanks for all your answers which give hours of work to understand 50%
    – ctac_
    May 24, 2018 at 19:53
  • grep '[^a-z]\{22\}' /usr/share/dict/words

    Looks for lines in the file /usr/share/dict/words which contain a string of 22 characters which are not lowercase letters. It is very likely that the file won't contain any such line. (Why would that file contain strings of 22 non-letters?)

  • grep -v '[a-z]\{22\}' /usr/share/dict/words

    Looks for lines which do not contain strings of 22 letters. There will probably be very many such lines. (Because most words are shorter than 22 letters.)

  • can we use ! and ^ alternatively May 24, 2018 at 13:27
  • @Neo_Returns: [^a-z] matches one character which is not between a and z in the current locale collation sequence. You need 22 of those to match [^a-z]\{22\}. I don't understand what you mean by "use ! and ^ alternatively".
    – AlexP
    May 24, 2018 at 13:32
  • I mean to say does [!a-z] and [^a-z] mean the same May 24, 2018 at 13:34
  • @Neo_Returns: No they don't, at least on any grep that I have access to. Once upon a time they did mean the same inside [...], but those times are long gone.
    – AlexP
    May 24, 2018 at 13:43
  • 2
    @AlexP, [!...] and [^...] mean the same in shell pattern matching... in most shells (including Debian's/Ubuntu's dash). Technically, the standard still only approves [!...] and leaves [^...] undefined, so if you look search wide enough, you might come across a shell that doesn't accept [^...]. But as far as I know, it's always been [^...] in regular expressions, which are what grep uses.
    – ilkkachu
    May 24, 2018 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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