0

I would like to check if a file name matches a certain pattern start with an alphanumeric character ([a-zA-Z0-9]) and will only contain alpha-numeric characters plus '.', '-' and '_' characters.

fileNamePattern="^[a-zA-Z0-9]+[\.-_]*"
if [[ $i(file from input) == $fileNamePattern ]]
      then
         echo "success"
      else
         echo "fail"
fi

This is how I tried to solve it but failed telling me unexpected operator after "[[" Please help me with it.

  • Is the (file from input) verbatim or a note? This should really be put as a comment or left out – Torin Jul 4 '19 at 11:53
  • shellcheck.net is a good starting point for errors like "unexpected operator". – roaima Jul 4 '19 at 11:55
  • Since you tagged this with dash, can we presume that you'd want to use that shell and not a shell that actually has a regular expression matching operator, like =~ in bash? – Kusalananda Jul 4 '19 at 12:11
  • Yes, you can assume =~ is not available. It must be dash@Kusalananda – Hanzhao Lu Jul 4 '19 at 12:24
  • (file from input ) is actually a comment, its my bad put it in there sorry@Torin – Hanzhao Lu Jul 4 '19 at 12:25
4

If you convert your extended regular expression to a globbing pattern, then you can use case ... esac in dash to check whether the value of your variable matches it, without involving grep:

case $name in
    [a-zA-Z0-9]*[._-]*)
        echo success
        ;;
    *)
        echo failure
esac

The pattern would match a value that starts with any of the characters that matches [a-zA-Z0-9] and then also contains dot, underscore, or dash (which is what I presume you want to check for, even though your expression allows the last set of characters to not exist in the string). Note that I have moved the dash to the end of the bracketed expression as using a range between . and _ (.-_) makes little sense. I'm also assuming that you don't want to match literal backslashes. If you do, add \\ to the second bracketed expression.

Well, that looks nice, but it doesn't do what you want as * could still match a non-alphanumeric character that is not ., _ or -.

Let's try to invert the sense of the test, so we test for names that fail:

case $name in
    *[![:alnum:]_.-]*)
        echo failure
        ;;
    *)
        echo success
esac

Now we detect values that contains characters that we know we don't want in our value. We don't yet correctly test whether the value starts with an alphanumeric character though...

case $name in
    *[![:alnum:]_.-]*)
        echo 'failure (contains wrong characters)'
        ;;
    *)
        case $name in
            [[:alnum:]]*)
                echo success
                ;;
            *)
                echo 'failure (does not start with alnum)'
        esac
esac

This would do at most two tests, first checking whether the value contains a character that we don't want, and then, if it doesn't, checks whether it starts with the correct type of character.

  • Thank you so much, your solution solved my problem perfectly – Hanzhao Lu Jul 4 '19 at 14:00
2

POSIXly, to match a string against a regexp, you can use expr or awk:

bre_match_anchored_at_start() { expr "x$1" : "x$2" > /dev/null; }
ere_match() { awk 'BEGIN{exit(!(ARGV[1] ~ ARGV[2]))}' "$1" "$2"; }

Then you can use:

if bre_match_anchored_at_start "$string" '[a-zA-Z0-9][a-zA-Z0-9._-]*$'; then
  echo match
fi

Or:

if ere_match "$string" '^[a-zA-Z0-9][a-zA-Z0-9._-]*$'; then
  echo match
fi

Note however that what a-z, A-Z or 0-9 match is unspecified and on most systems rather random outside of the C/POSIX locale. You could instead use [[:alnum:]] (POSIX, but not supported by all awk implementations) which matches on all alphanumeric character (again, potentially a lot more than the 52 English letters and 10 decimal digits if not in the C locale). If you want to match on those only, you could do:

if (export LC_ALL=C; ere_match "$string" '^[a-zA-Z0-9][a-zA-Z0-9._-]*$'); then
  echo match
fi

You can also achieve the same with a standard shell construct here with a negative approach:

case $string in
  ("" | [!a-zA-Z0-9]* | *[!a-zA-Z0-9._-]*) ;;
  (*) echo match;;
esac
0

If [[ "$filename" =~ pattern ]] is not available, you could use grep:

if printf '%s' "$filename" | grep -qz 'pattern'; then
    echo "success"
else
    echo "fail"
fi

Note, that grep uses basic regex as default. You can use extended regex (-E) or pearl compatible regex (-P) if available with your grep.

  • grep matches against each line of its input, not its whole input. Also it's behaviour is unspecified if the input is not newline delimited. To match strings against regexps, you can use expr (BRE) or awk (ERE). – Stéphane Chazelas Jul 4 '19 at 13:15
  • Added -z trying to fix the newline issue. But you're probably right that grep is not the best idea. Thanks for letting me learn :-) – pLumo Jul 4 '19 at 13:23

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.