0

I am looking for directories that contain files that match a pattern, say, foobar*.csv. Directories that do contain such files may contain hundreds of them (and no subdirectories!), so I want find to print here/is/the/path/to/dir as soon as it finds here/is/the/path/to/dir/foobar-2022-01-01.csv and stop descending.

How do I do that?

1
  • 1
    sounds like a job for a proper programming or scripting language, and not for find + shell scripting Commented Aug 30, 2023 at 17:01

5 Answers 5

0
find /path/to/base/dir -type f -name "foobar*.csv" -exec dirname {} \; | sort -u

Explanation:

  • Find all the files that meet the "foobar*.csv" pattern.
  • Get the dirname for each file.
  • Sort them unique.

This isn't memory efficient, but for "hundreds" of files it'll take a few milliseconds to get you the result.

4
  • it took almost 2 minutes (on a remote directory) but it works, thanks!
    – sds
    Commented Aug 30, 2023 at 18:10
  • With GNU find, you can make it (orders of magnitude) more efficient and more robust with LC_ALL=C find /path/to/base/dir -type f -name "foobar*.csv" -printf '%h\0' | LC_ALL=C sort -zu | tr '\0' '\n'. In any case, it doesn't satisfy the OP's requirement of stopping at first match and not recursing. Commented Aug 30, 2023 at 18:15
  • There isn't any general purpose tool that stops at first match. The requirement can't be satisfied but by a custom program.
    – Daniel J.
    Commented Aug 31, 2023 at 13:41
  • -exec dirname -- {} + would be faster Commented Aug 31, 2023 at 16:13
0

With zsh:

dirs=( **/*(/e['()(($#)) $REPLY/foobar*.csv(NY1)']) )

That doesn't prevent descending in that it will still try to find subdirs of matching dirs, Y1 will stop at the first match of foobar*.csv, but zsh will still need to read the full contents of all (non-hidden) directories to find subdirectories).

0

With the (POSIX-like) bosh shell, where find (actually sfind) is builtin and has some -call/-calldir predicates that can evaluate shell code:

find_match() {
  find . ! -name . -prune -name "$1" -call 'pwd; return' ';'
  false
}
find . -type d -calldir 'find_match "foobar*.csv"' ';' -prune

To do anything with those matching directories, add a:

-call '
  dir=$1
  whatever you need to do with "$dir"
' {} ';'

To that command above.

-prune like in standard find is what stops find from descending into subdirectories. AFAICT, sfind doesn't have a GNU-style -quit or NetBSD-style -exec [status], but it looks like calling return from a -call[dir] called within a function can achieve the same effect and makes it even more flexible.

0

Using find to find all directories, and for each directory, try to expand the given globbing pattern. If the pattern is expanded to something that exists, print the directory path and don't descend further.

find . -type d \
    -exec sh -c 'set -- "$1"/foobar*.csv; [ -e "$1" ]' sh {} \; \
    -print -prune
0

Using find's -execdir option instead of -exec:

find . -iname 'foobar*.csv' -execdir sh -c 'pwd' {} +

This uses a sh -c wrapper around pwd (instead of just running -execdir pwd {} +) because pwd doesn't take filename args and will complain if given them. The shell wrapper allows pwd to be run in a directory without passing the filename args to it.

Alternatively, if there is any chance that there might be more matching files in a directory than will fit into ARG_MAX (about 2MB on Linux so this is very unlikely), pipe the output into uniq.

find . -iname 'foobar*.csv' -execdir sh -c 'pwd' {} + | uniq

NOTE: -execdir is a non-standard enhancement to the POSIX find command. It may be missing on some versions of find (especially ancient and/or proprietary versions). It is available in both GNU find and BSD find.

You must log in to answer this question.

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