Regex match pattern with optional character and min/max length

Viewed 524

Match starts with X, which is followed by 3 to 6 capital words or numbers, must containt at least one letter and can have optional dash -. Example of matches:

XABC
XA-BC8D
X-ABC
XB72D-

Example of non-matches:

X123      //no letters
XAB       //too short
XABC-123  //too long
XA--BC    //too many -

I tried with X(?=.{3,6}$)[A-Z0-9]*-?[A-Z0-9]*, but it has many problems. It can match something like X---. Regex should be compatible with grep.

2 Answers

You may use this regex with gnu grep using -P (PCRE):

grep -P '^X(?!([^-]*-){2})(?=[^A-Z]*[A-Z])[A-Z\d-]{3,6}$' file

XABC
XA-BC8D
X-ABC
XB72D-

RegEx Demo

RegEx Details:

  • ^: Start
  • X: Match letter X
  • (?!([^-]*-){2}): Negative lookahead to assert that there are never more than one hyphens ahead
  • (?=[^A-Z]*[A-Z]): Positive lookahead to assert presence of at least one uppercase letter ahead
  • [A-Z\d-]{3,6}: Match an uppercase letter or digit or - 3 to 6 times
  • $: End

If you don't have gnu grep installed then you may consider this awk:

awk -F- 'NF<3 && /^X[A-Z0-9-]{3,6}$/ && /.[A-Z]/' file

XABC
XA-BC8D
X-ABC
XB72D-

You can use a grep with a PCRE pattern using -P option (or pcregrep if grep is not available):

^X(?!(?:[^-]*-){2})(?=.*\p{Lu})[\p{Lu}\d-]{3,6}$

See the regex demo. Details:

  • ^ - start of string
  • X - an X char
  • (?!(?:[^-]*-){2}) - there can't be two not necessarily consecutive hyphens in the string
  • (?=.*\p{Lu}) - there must be an uppercase letter in the string
  • [\p{Lu}\d-]{3,6} - three to six alphanumeric or - chars
  • $ - end of string.

Here is a way with awk:

awk '/^X[[:upper:][:digit:]-]{3,6}$/ && /.[[:upper:]]/ {val=$0; sub("-","",val); if (val ~ /^X[[:upper:][:digit:]]+$/) {print $0;}}' file

A bit shortened version:

awk '/^X[[:upper:][:digit:]-]{3,6}$/ && /.[[:upper:]]/ {val=$0; sub("-","",val); val ~ /^X[[:upper:][:digit:]]+$/}1' file

See an online demo. Details:

  • /^X[[:upper:][:digit:]-]{3,6}$/ && /.[[:upper:]]/ - the line must contain an uppercase letter that is not first in the string, and must match the ^X[[:upper:][:digit:]-]{3,6}$ pattern:
    • ^ - start of string
    • X - a X
    • [[:upper:][:digit:]-]{3,6} - three to six alphanumeric or - chars
    • $ - till end of string
  • val=$0; sub("-","",val); - the line is assigned to val variable and then a single - is removed from val
  • if (val ~ /^X[[:upper:][:digit:]]+$/) {print $0;}} - if the val starts with X and then has only uppercase letters or digits till end of string, print it. In the shortened version, that if $0 ~ /.../ {print $0;} is replaced with /.../ and 1 flag (that by default prints the whole record (line).
Related