[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}.*)!$'
[^a-z]
andgrep -v
.