I'm trying to find a UNIX pattern which will match only numerical file extensions. For example, it will match

  • file.1
  • file.2
  • file1.1
  • 5file2.52
  • file.25938

etc. but will NOT match

  • file1.0.ext
  • file4.csv
  • 6file5.5.2.ext
  • file.1s
  • file.s134

I thought this would be easy but I can't seem to get it - I have tried

  • *.[0-9]* which matches file1.0.csv
  • *.[0.9]*/> which matches nothing

Any experts out there know what I'm missing? I thought /> matched at the end of a word.

edit - clarification: I'm coding in python using a module that uses unix shell-style wildcards, so I only have access to said wildcards

  • Globs are not regular expressions, if you just want things that end in a number you could do *[0-9] but that would match, say, file.csv2 which may not be what you want – Eric Renouf Sep 16 '16 at 15:16
  • you're correct - i need to match only a pattern of .[0-9]+ at the END of a string. do you know how that is possible? – Nate Sep 16 '16 at 15:20

With zsh:

echo file.<->

<-> being a special form of <x-y> like <1-100> but that match any decimal positive integer without restriction (any non-empty sequence of decimal digits). Or with the extendedglob option:

echo file.[0-9]##

## being the equivalent of the + extended regular expression operator.

With ksh or bash -O extglob (or after shopt -s extglob within bash) or zsh -o kshglob (or after setopt kshglob within zsh):

echo file.+([0-9])

Again, +(x) is like ERE x+.

ksh93 can also use EREs in its glob with:

echo ~(E).*\.[0-9]+$

(the .* is not strictly necessary here, it's just that without it, ~(E)\.[0-9]+$ would also match hidden files)

Standard globs don't have an equivalent. All they have are:

  • * to match any number of characters
  • ? to match any single character
  • [criteria] to match one character that matches the criteria.

you'd need to post-filter the list like:

set -- *.*[0-9]
for i do
  case ${i##*.} in
    (*[!0-9]*) ;;
    (*) set -- "$@" "$i"
echo "$@"

That is, from the list of file names that contain a . and end with a digit, exclude those where the part after the last . contains a non-digit.

| improve this answer | |
  • in fact I am coding in python with a module (fnmatch) that uses UNIX shell-style wildcards, so I don't have access to any additional tools. is what I am looking for not possible? I didn't think a python stackexchange was the right place to inquire. – Nate Sep 16 '16 at 15:22
  • @Nate, not with standard globs unless you want to add all *.[0-9],*.[0-9][0-9], *.[0-9][0-9][0-9]... In POSIX shells, the globs that don't match expand to themselves, which would be a problem with that approach, but maybe the approach is applicable to your python case. Can you not use regular expressions instead? – Stéphane Chazelas Sep 16 '16 at 15:37
  • given my inputs, i was able to get away with NAME*[0-9]. – Trevor Boyd Smith Jul 3 '19 at 13:10

In bash (if $f contains the filename to check):

[[ "${f##*.}"  == +([0-9]) ]] && echo "Yes" || echo "No"

Where "${f##*.}" selects the last extension (after the last dot),
and +([0-9]) is an extended regex to match only numbers.

A sample script to process a list of filenames (inside infile) may be:

while read f; do
    printf 'file %-15s ----> %7s ==> ' "$f" "${f##*.}"
    if     [[ "${f##*.}"  == +([0-9]) ]]
    then   echo "Yes"
    else   echo "No"
done <"infile"

Which outputs:

file file.1          ---->       1 ==> Yes
file file.2          ---->       2 ==> Yes
file file1.1         ---->       1 ==> Yes
file 5file2.52       ---->      52 ==> Yes
file file.25938      ---->   25938 ==> Yes
file NOT             ---->     NOT ==> No
file file1.0.ext     ---->     ext ==> No
file file4.csv       ---->     csv ==> No
file 6file5.5.2.ext  ---->     ext ==> No
file file.1s         ---->      1s ==> No

The main hurdle to overcome with (UNIX) simpler shells is to find utilities that understand and execute (Extended) regex.

You may try with "Extended Regex" in sed:

[ "$(echo "${f##*.}" | sed -nE 's/^([0-9]+)$/\1/p')" ] && echo "Yes" || echo "No"

or "Basic Regex", also in sed:

[ "$(echo "${f##*.}" | sed -n 's/^\([0-9]\{1,\}\)$/\1/p')" ] && echo "Yes" || echo "No"

or expr (you may need to change \+ to \{1,\} under Solaris and others):

expr "${f##*.}" : '^\([0-9]\+\)$' >/dev/null && echo "Yes" || echo "No"

or even awk:

[ $( echo "${f##*.}" | awk '/^[0-9]+$/' ) ] && echo "Yes" || echo "No"
| improve this answer | |

The python fnmatch module that you want to use converts a given file "glob" type argument to a python regex (re) but it does not handle '+' operator in the way I had hoped: it seems to get escaped by re.escape() (looking at the source for fnmatch in python 2.6 on my system)


fnmatch.filter(['file.007'], '*.[0-9]+')

does not work as we would expect, although

fnmatch.filter(['file.7'], '*.[0-9]')

does give a match.


 fnmatch.filter(['file.007'], '*.[0-9]{1,}')

is no good.

| improve this answer | |
  • Yes, for fnmatch a + is a litteral +, *.[0-9]+ is meant to match on strings that end in ., a decimal digit and a +. The fnmatch patterns as defined by POSIX are not extensible, you'll notice that all the shells that have extended patterns have it under some option, or in the case of ksh, only in places where () would otherwise be invalid when not quoted (as in p='+(x)'; case x in $p) echo match; esac doesn't work in ksh but case x in +(x)) echo match; esac does as that code is otherwise unspecified in POSIX). – Stéphane Chazelas Sep 16 '16 at 20:10

This can not be done directly with standard filename globbing. You may however test the extension separately:

Assuming you want to test a filename in a variable:

case ${filename##*.} in
    *[!0-9]*) echo 'This is not the filename you are looking for' ;;
    *[0-9]*)  echo 'This is the file!'

This first strips everything from the start of the filename to the last dot, leaving only the extension bit. It then tries to match this (possibly empty) string against the glob pattern *[!0-9]*, which will succeed if the string contains a non-digit. The second test is just to be sure we actually have a digit and not just an empty string in the extension.

In your Python code, you could then first generate a complete listing of all files, take those filenames and strip the bit before the extension off, and then test the extension with *[!0-9]*, if you really need to use globbing.

I have a feeling that there are more efficient ways of doing this in Python though.

| improve this answer | |

your files end with digit, so just use *[0-9]

| improve this answer | |
  • But the OP wants a pattern that doesn’t match file.s134; i.e., all the characters in the extension must be digits. – Scott Jun 15 '18 at 5:33

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.