Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

How can I get a list of the subdirectories which contain a file matching a particular string?

More specifically, I am looking for directories which contain a file with the letter 'f' somewhere occurring in the file name.

Ideally, the list would not have duplicates and only contain the path without the filename.

share|improve this question
up vote 10 down vote accepted
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

The above finds all files below the current directory (.) that are regular files (-type f) and have f somewhere in their name (-name '*f*'). Next, sed removes the file name, leaving just the directory name. Then, the list of directories is sorted (sort) and duplicates removed (uniq).

The sed command consists of a single substitute. It looks for matches to the regular expression /[^/]+$ and replaces anything matching that with nothing. The dollar sign means the end of the line. [^/]+' means one or more characters that are not slashes. Thus, /[^/]+$ means all characters from the final slash to the end of the line. In other words, this matches the file name at the end of the full path. Thus, the sed command removes the file name, leaving unchanged the name of directory that the file was in.


Many modern sort commands support a -u flag which makes uniq unnecessary:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

Also, if your find command supports it, it is possible to have find print the directory names directly. This avoids the need for sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

More robust version (Requires GNU tools)

The above versions will be confused by file names that include newlines. A more robust solution is to do the sorting on nul-terminated strings:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
share|improve this answer

There are essentially 2 methods you can use to do this. One will parse the string while the other will operate on each file. Parsing the string use a tool such as grep, sed, or awk is obviously going to be faster but here's an example showing both, as well as how you can "profile" the 2 methods.

Sample data

For the examples below we'll use the following data

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Delete some of the *f* files from dir1/*:

$ rm dir1/dir10{0..2}/*f*

Approach #1 - Parsing via strings

Here we're going to use the following tools, find, grep, and sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5

Approach #2 - Parsing using files

Same tool chain as before, except this time we'll be using dirname instead of grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5

NOTE: The above examples are using head -5 to merely limit the amount of output we're dealing with for these examples. They'd normally be removed to get your full listing!

Comparing the results

We can use time to take a look at the 2 approaches.


real        0m0.372s
user        0m0.028s
sys         0m0.106s


real        0m0.012s
user        0m0.009s
sys         0m0.007s

So it's always best to deal with the strings if possible.

Alternative string parsing methods

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u


$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u


$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u
share|improve this answer
+1 Because it works, but interestingly this takes many times longer than @John1024's answer – Muhd Feb 1 '14 at 2:49
@Muhd - yes the calls to dirname are slow. I'm working on an alternative. – slm Feb 1 '14 at 2:50

Why not try this:

find / -name '*f*' -printf "%h\n" | sort -u
share|improve this answer
Best answer. Entirely POSIX-compatible, unlike some answers above, above, and also earns the special The Shortest Pipeline prize :). – kkm Aug 18 '15 at 21:04
I would love to see someone show the timing of this vs the others above, because I get the feeling this is by far the fastest. – dlamblin Oct 18 '15 at 16:40
@kkm I agree this is the best solution but POSIX specs for find are actually quite sparse—the -printf operator is not specified. This doesn't work with BSD find. So, not "entirely POSIX compatible." (Though sort -u is in POSIX.) – Wildcard Apr 1 at 18:54

This answer is shamelessly based on slm answer. It was an interesting approach, but it has a limitation if the file and/or directory names had special chars (space, semi-column...). A good habit is to use find /somewhere -print0 | xargs -0 someprogam.

Sample data

For the examples below we'll use the following data

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Delete some of the *f* files from dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Approach #1 - Parsing using files

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

NOTE: The above examples are using head -5 to merely limit the amount of output we're dealing with for these examples. They'd normally be removed to get your full listing! also, replace the echowhich whatever command you want to use.

share|improve this answer

Here's one I find useful:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.