I want to write a regular expression that only allows a user to enter an input which contains alphanumeric characters, underscores, periods, dashes, and plus signs.

In a bash script, I have:

if [[ ! $THE_INPUT =~ $VALID ]]; then
    echo "ERROR"
    exit 1  

Why does this not work when the user enters an input which has a - in it?

Example user inputs:

  1. great-food.png

    i. produces error

  2. great_+food.png

    i. doesn't produce error

2 Answers 2


The [0-9a-zA-Z_\.\-\+] bracket expression in POSIX Basic or Extended¹ regular expressions matches:

  • 0-9: collating elements (not even necessarily single characters!) ranging between 0 and 9 (could be anything depending on the locale, so that should not be used for input validation outside the C/POSIX locale)
  • a-zA-Z: same for a to z and A to Z.
  • _\.: either underscore, backslash or period:
  • \-\: collating elements sorting between \ and \. Likely only \ but you never know.
  • \+: backslash again and +.

As \ is not special inside bracket expressions.

To include a literal - in the set, it needs to be placed last or first (or right after ^ in negated sets).

bash uses POSIX Extended regexps for its =~ operator.

Here, you'd want:


If switching to zsh is an option, you could use its extended glob operators which are a superset of extended regexps with a different syntax and behave less surprisingly, and can work with non-text contrary to regexps:

set -o extendedglob

if [[ $input != $~VALID ]]; then...

(note the ~ for the contents of $VALID to be taken as a pattern here instead of literally).

It also has a [:WORD:] character class that matches any character classified as alnum in the locale plus the ones in $WORDCHARS.

In the C locale, you could define WORDCHARS='.-+_' and then use:

set -o extendedglob # needed for ##, the equivalent of ERE +.

zsh also has support for PCREs which also have non-surprising bracket expressions:

zmodload zsh/pcre

if [[ ! $input -pcre-match $VALID ]]; then...

Or make =~ use PCRE instead of ERE:

set -o rematchpcre

if [[ ! $input =~ $VALID ]]; then...

You could also use VALID='^[0-9a-zA-Z\-._+]+\z' there.

Beware that PCRE doesn't support multibyte charsets other than UTF-8, and the above reports errors in UTF-8 locales if $input is not valid UTF-8.

In the C locale, you could also use VALID='^[\w.+-]\z', where \w matches alnums and underscore.

¹ awk being an exception as it does use extended regexps but \ is still special within bracket expressions or at least it is in some implementations.

  • What is a "locale" in the context of regex? Commented Apr 3, 2023 at 23:51
  • @user3731622, see man 7 locale on your system for instance. Here for regexp, what matters are the LC_CTYPE category including the charmap which determines how sequence of bytes are decoded into text and the character classifications (alpha, digit, alnum as used in [[:alnum:]], \w or zsh's [[:WORD:]]), and the LC_COLLATE category that defines how text is collated (that the character sorts somewhere between k and l or that the ddzs collating element sorts between d and e in a hu_HU Hungarian locale for instance). Commented Apr 4, 2023 at 8:07

Usually in a regex, the - character in a character class block implies a range (e. g. [a-z]. The usual way to express that you are not meaning to imply a range is to put the hyphen at the very end of the character class descriptor. When I do so in your script, it seems to work as you wish:



$ ./741634.sh parameter/with/slashes
$ ./741634.sh parameter-with-dashes
  • 2
    And you shouldn't need to escape any of those characters unless you want backslash to be part of the expression.
    – Kusalananda
    Commented Apr 1, 2023 at 6:35

You must log in to answer this question.

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