# how to find files based on filename range?

I have some files with numeric names: `2341a.po`, `4567211someword.po`, `0012.po`, etc. I would like to find a set of files based on the numeric range. such as `[126 - 363]`.

Normally I use regular expression with `find`. Any numeric range `[N, M]` can be composed with two regular expressions `larger than N` and `less than M`.

### The larger than N:

If `N = vxyz`, then I first do a match for all `value > V000, (V=v+1)` with `[V-9]\d{3,}`

Then match `vX00`, `X=x+1`, with `v[X-9]\d\d`

Then match `vxY0`, `Y=y+1`, with `vx[Y-9]\d`

And last `vxy[z-9]`

Example:

To match `number>=234`, I use:

```````^(0*([3-9]\d{2,}|2[4-9]\d|23[4-9]))`
``````

### The smaller than M:

Based on similar logic we will have:

``````^(0*(vxy[0-z]|vx[0-Y]\d|v[0-X]\d\d|[1-V]\d\d|\d{1,3}))[^0-9]
``````

With `Y=y-1,X=x-1,V=v-1`

For example, the follow command will find any file between [253, 326]:

``````find . -maxdepth 1 -type f -regextype posix-extended -iregex '^\./0*([3-9][0-9]{2,}|2[6-9][0-9]{2,}|25[3-9]).*' -iregex '^\./0*(32[0-6]|3[0-1][0-9]|[0-2][0-9][0-9]|[0-9]{1,2})[^0-9].*'
``````

However, this method is too annoying to handle long numbers. Is there any better and easier way to do this?

## 4 Answers

With `zsh`:

``````setopt extendedglob # best in ~/.zshrc
ls -ld -- <126-253>(*.po~[0-9]*)
``````

Recursively, including in hidden dirs and only regular files:

``````ls -ld -- **/<126-253>(*.po~[0-9]*)(D.)
``````

use `zargs` if you run into the `arg list too long` error.

You could generate a list of files with the numbers that you want to find and put them on the argument list for `find` using `xargs`. For example, using `bash`, the equivalent to using `-maxdepth 1` as in the question would be:

``````echo {253..326} | xargs sh -c 'find "\$@" -type f -maxdepth 0' sh
``````

Note that you could use the `-I` option to `xargs` (ie `xargs -I{} find {} -type f`), but GNU `xargs` forces `-L 1` with this option, meaning that a separate find process is started for each argument. Using `sh` gets around this issue.

For an arbitrary depth you can do:

``````printf -- '-o -name %d ' {254..326} |
xargs -n 3000 sh -c 'find -type f \( -name 253 "\$@" \)' sh
``````

The `-n` argument (maximum number of arguments added per command) should be chosen so it will limit the size of the argument list `xargs` constructs. If it is too large the limit may be reached due to the overall size of the argument list rather than the number of arguments. It should be a multiple of 3 so that you are not left with a trailing `-o` or `-name` in the list.

• Great! This is a very smart solution! However, if the list is very long and there are lots of files, it needs to repeat the `find` too many times. Thus this method seems take extremely longer time than regex method. Any comment on this? – Wang Dec 30 '14 at 2:25
• @Wang, it shouldn't be starting too many different `find` processes. If it is, that sounds like an issue with the version of `xargs` you are using. I never tested this on a large number of files though, it could be generally quite slow even with one process. A lot depends on the `find` implementation which probably isn't optimised for this kind of thing. What was the command you used? Did you add any globs to the search string? – Graeme Dec 30 '14 at 10:55

A brute-force extension on Graeme's answer:

``````find . -type f -regextype posix-awk -regex ".*/0*(\$(seq -s'|' 254 456)).*"
``````

You can use `\|` instead of `|` and `\(\)` instead of `()` if you need POSIX (but then, `seq` is not POSIX, is it?).

``````\$ find . -maxdepth 2 -type f -regextype posix-awk -regex ".*/0*(\$(seq -s'|' 254 456)).*"
./.fontconfig/3047814df9a2f067bd2d96a2b9c36e5a-le32d4.cache-3
./.fontconfig/3830d5c3ddfd5cd38a049b759396e72e-le32d4.cache-3
./.fontconfig/385c0604a188198f04d133e54aba7fe7-le32d4.cache-3
./Documents/374620-63301.pdf
./4567211someword.po
``````

Hmm. Looks like we should add a non-digit character to mark the end of the number. Perhaps `".*/0*(\$(seq -s'|' 254 456))[^0-9].*"`?

• The problem here is that it won't work if the argument list is too long. The reason for preferring `xargs` in a situation like this is that it will just start another `find` process once the current argument list is full. – Graeme Dec 26 '14 at 11:17
``````find | perl -ne 'print if(m!^\./(\d+)! and \$1 > 126 and \$1 <363)'
``````

...possibly adding some of the good ideas presented in the other answers.

Regex may need some tuning (eg: `^\./(\d+)\w*.po\$`)