2

I am trying to match some image files which share a basic name but have sizing information appended to the end with find . -name 'Name*' to match files such as:

Name.jpg
[email protected]
Name-180x101.jpg
[email protected]

However my glob pattern also matches:

NameLonger.jpg
[email protected]
NameLonger-180x101.jpg
[email protected]

The second group is undesired.

How can I adapt my find command to hit the first group but ignore the second? Is it possible with a glob pattern, or is a regex needed?

Note: For this case, the sizing information (-180x101) is always "-" then any number of numerical digits , then "x", then any number of numerical digits. If a file includes '@2x' it is always at the end.

Note: It must also hit on the plain file, e.g: Name.jpg.

Thanks in advance.

1 Answer 1

5

With zsh, to be specific about the format of file names you want:

print -rC1 -- **/Name(-<->x<->|)(@<->x|).jpg(DN)
  • print -rC1: print raw on 1 Column
  • **/: recursive globbing
  • (a|b): alternation, so (x|) is like ERE (x)?
  • <x-y>: sequence of decimal digits representing integer numbers x to y, <-> sequence of digits representing any number.
  • D: dotglob: also look in hidden dirs like find does.
  • N: nullglob: don't complain if there's no match and expand to nothing instead.

A GNU find equivalent (without the sorting of files and with ./ prefixed to each path):

LC_ALL=C find . -regextype posix-extended -regex \
  '.*/Name(-[0-9]+x[0-9]+)?(@[0-9]+x)?\.jpg'

(with BSD find, use find -E . instead of find . -regextype posix-extended)

With standard find, maybe:

find . -name '.*/Name[-@.]*'

Would be enough if all you need is exclude NameLonger.

The simple wildcard patterns have no equivalent for the +, ? or | operators. It would be possible to do something equivalent with a number of -name and ! -name ANDed and ORed together (like accept Name-*x*.jpg but reject Name-*[!0-9]*x*[!0-9]*.jpg, Name-x*, Name-*x.jpg, etc) but that would be extremely cumbersome as you'd need hundreds of those to handle all case.

The GNU libc implementation of the fnmatch() function (as used by find -name) can be told to support some ksh88 extended glob operators (FNM_EXTMATCH flag) which bring regex equivalence (the +(...), ?(...)... also supported by zsh -o kshglob or bash -O extglob), but AFAIK, not even the GNU implementation of find can be told to use them.

POSIXly, you could resort to awk to refine the matching:

LC_ALL=C find . -name 'Name*' -exec awk '
  BEGIN {
    for (i = 1; i < ARGC; i++)
      if (ARGV[i] ~ "/Name(-[0-9]+x[0-9]+)?(@[0-9]+x)?\\.jpg$")
        print ARGV[i]
  }' {} +

(bearing in mind that it could fail if the paths of some files are too long to fit in a command line. Not all find implementations are able to find files of paths longer than PATH_MAX anyway).

2
  • zsh command was very useful and performed very well, but I needed this on a webserver where I am not able to change the shell. I modified your GNU find example in the context of my needs by making the regex hit any file extension, and added a sub-directory in the tree to ignore: find ./path/to/files -regextype posix-extended -regex '.*/FileName(-[0-9]+x[0-9]+)?(@[0-9]+x)?\.[^.]+$' -not -path './path/to/files/skip/*' This worked very well to find duplicates in neighboring directories running across nearly 10k input files and covering 50GB of files. Thank you.
    – tlock
    Jun 23, 2021 at 16:20
  • @tlock, would be better as find ./path/to/files -regextype posix-extended -path ./path/to/files/skip -prune -o -regex '.*/FileName(-[0-9]+x[0-9]+)?(@[0-9]+x)?\.[^/.]+$' -print i.e. prune the paths you want to skip and make sure you're not including / in the run-away parts of your regexp. Jun 23, 2021 at 16:26

You must log in to answer this question.

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