2

Given a directory X that contains several subdirectories and files, I would like to list the names of the immediate child directories of X that (directly or indirectly) contain file names matching the regex ^rf.*\.img$.

How can this be achieved on OS X?

  • 1
    Which OS? You probably have find installed, which version? – muru Nov 16 '15 at 11:52
  • I'm on OSX, but it seems as though find doesn't have a -v flag, or equivalent. How should I check the version? – blz Nov 16 '15 at 11:54
  • 1
    Ok, just see if find supports -regex: man find | grep -e -regex – muru Nov 16 '15 at 11:54
  • @muru: no such luck – blz Nov 16 '15 at 11:57
  • 1
    Ok, however, in your case the filename can be expressed using wildcard patterns: rf*.img. So would be fine with that? – muru Nov 16 '15 at 11:58
3

With GNU find, you can test each directory:

for i in "$dir"/*/
do
    test -n "$(find "$i" -type f -regex '.*/test[^/]*' -print -quit)" \
       && echo "$i"
done

This searches each directory for a file beginning test and prints the directory if it's found.

A couple of things to note:

  1. I'm ignoring subdirectories that begin with .; if they need to be considered, check the Bash FAQ for how to include them without also catching . and ...
  2. We exit find early, using -quit when we get a match. This may save time in large filesystem trees.
  3. The -regex test matches against the whole pathname considered by find, so if we just want to match against the basename, we need to be careful about matching /. We can match on wildcard patterns without this restriction (and without requiring GNU find), using -filename.
  4. You can use -iregex instead of -regex for a case-insensitive match.
  5. You can select the regex syntax using -regextype option.

For the specific pattern in the question, we can use wildcards, so it becomes:

for i in "$dir"/*/
do
    test -n "$(find "$i" -type f -name 'rf*.img' -print -quit)" && echo "$i"
done
2

With zsh (assuming you're already in X):

typeset -U subdirs
subdirs=(*/**/rf*.img(.e_'REPLY=${REPLY%%/*}'_))
print -rl -- "${subdirs[@]}"

Here */**/rf*.img globs for all rf*.img files in all subdirectories, the glob qualifiers . and e select only regular files and respectively save only the first component of the path in an array that was initially defined as an array with unique elements (so no duplicates). It then prints each element of the array.

1

You can get a list using find and pipe that through grep:

(cd X && find . -type d -maxdepth 1 | sed -e 's,^./,,' | grep -E '^rf.*\.img$' )

I used a subshell to get just the names inside X; otherwise one would need to account for the path in find.

If you want subdirectories recursively, you can omit the -maxdepth 1 option. But then that means you would have to adjust the regular expression, e.g.,

(cd X && find . -type d | sed -e 's,^./,,' | grep -E '^(.*/)?rf.*\.img$' )

and in that case, the sed command is redundant (but harmless).

  • This is 99% correct, but I was a bit unclear: the child directories of X also have child directories of their own, which may contain the files I'm trying to match. To be clear: I am only looking to list the names of X's children, however, so that part is correct. – blz Nov 16 '15 at 11:56
1

Use neither grep nor ls, but find:

find . -name 'rf*.img'

GNU find has an option to print just the directory (-printf "%h\n"), but in your case, that's not an option.

You could do something like:

find . -name 'rf*.img' | sed 's:/[^/]*$::'

If you want just the first directory, maybe something like:

find . -name 'rf*.img' -exec dirname {} \; | sed 's:./\([^/]*\).*:\1:'
  • That's a glob, which doesn't correspond exactly to the regex. (OP may still accept though). – Thomas Dickey Nov 16 '15 at 12:05
  • I like this approach -- it's much simpler than a regex. One small problem: it's listing the full path, and I only want the first-level child of the current working directory. Is there a fix for this? i.e., it prints ./2015_02_17_THRESHOLD_Pilote01/S15_MBB3_ep2d_TR_900_3iso_Run6 whereas I only want ./2015_02_17_THRESHOLD_Pilot01. – blz Nov 16 '15 at 12:05
  • @ThomasDickey I think they would. But where does this pattern differ from the regex? – muru Nov 16 '15 at 12:06
  • @blz I thought that's what you wanted! :D I really wish you had GNU find. See update – muru Nov 16 '15 at 12:10
0

Use perl and its grep and glob.

#!/usr/bin/env perl
use strict;
use warnings;

my @file_list = grep { /^rf.*\.img$/ } glob "*";
print join ( "\n", @file_list );

Or as a one-liner:

perl -e 'print join ( "\n", grep { /^rf.*\.img$/ } glob "*" )'

Note - glob does shell-style wildcard expansions, so you can glob "*/*/name/*" or similar. If you need an arbitrary recursion depth, you might want to use File::Find instead:

#!/usr/bin/env perl
use strict;
use warnings;

use File::Find;

sub print_match {
    #Default match is against $_ which is filename. 
    #can match $File::Find::name for full name.
    #or $File::Find::dir
    print if  /^rf.*\.img$/;
}

find ( \&print_match, "/path/to/search" );

As a one-liner again:

perl -MFile::Find -e 'find ( sub { print if  /^rf.*\.img$/ }, "/my/path/to/search" );'

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.