3

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 4

2

With zsh:

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

That is, decimal numbers 126 to 253 (000126 also accepted) followed by something that ends in .po and doesn't start with a decimal digit).

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.

To use variables instead of literal numbers, you can't do <$low-$high>. That <x-y> operator overlapping with redirection operators (echo <3-4> z in POSIX shells, runs echo with input redirected from the 3- file and fd 4 going into z), zsh tries to minimize the risk of conflict by accepting only literal digits. You can however use use that operator as part of globsubsting expansions like:

low=126 high=253
ls -ld -- ${~:-"<$low-$high>"}(*.po~[0-9]*)

Where ${~expansion} enable globsubst (allows the expansion to undergo globbing) for the expansion and ${:-"text"} is a special form of ${var:-default} to have an expansion expand to arbitrary text.

2
  • How would your run this if 126 and 253 are stored in a variable? So ls -ld -- <$FIRST_RANGE-$LAST_RANGE>(.po~[0-9])
    – hfranco
    Jan 3, 2020 at 23:05
  • 1
    @hfranco, see edit. Jan 4, 2020 at 9:37
1

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.

2
  • 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, 2014 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, 2014 at 10:55
0

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].*"?

1
  • 2
    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, 2014 at 11:17
0
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$)

You must log in to answer this question.

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