4

I have a solution for my question

find . -type d -exec sh -c 'test $(find "$0" -maxdepth 1 -type d | wc -l) -eq 1' {} \; -print

I wonder, whether there is a better (faster) method to do this. I don't really like to start in a 'find' another find process.

boxdot
  • 203
  • 1
  • 7
  • 2
    Maybe this one can give you ideas [http://stackoverflow.com/questions/1574403/list-all-leaf-subdirectories-in-linux](http://stackoverflow.com/questions/1574403/list-all-leaf-subdirectories-in-linux) – epsilon Jul 25 '13 at 12:24

2 Answers2

4

man find would list an option:

   -links n
          File has n links.

You're looking for directories that contain only two links (namely . and it's name). The following would return you directories without subdirectories:

find . -type d -links 2

Each directory on a normal Unix filesystem has at least 2 hard links: its name and its . (parent directory) entry. Additionally, its subdirectories (if any) each have a .. entry linked to that directory.

devnull
  • 118,548
  • 33
  • 236
  • 227
  • The output of `find . -type d -links 2` is empty for a directory lying on a ntfs partition. For ext3 filesystem this solution works. – boxdot Jul 25 '13 at 12:38
  • 1
    @finite You're right. However, it would work well on local filesystem, and that was the *assumption*. – devnull Jul 25 '13 at 12:40
  • Actually this give the right result, but not for the reason explained. Any directory has at least 2 hard links (the directory itself and `.`), any subdirectory adds 1 to the count (the `..` inside the subdirectory). – toro2k Jul 25 '13 at 13:06
  • @toro2k: Create a sub directory with just one file and this command doesn't list that sub dir. – anubhava Jul 25 '13 at 13:33
  • 1
    Hmmm... `mkdir -p foo/bar/baz; touch foo/bar/baz/something; find foo -type d -links 2` returns `foo/bar/baz` – devnull Jul 25 '13 at 13:46
  • @devnull: Do a `mkdir abc; touch abc/pqr.txt` and then this find command won't list `abc` in output. – anubhava Jul 25 '13 at 14:58
1

With little more coding following commandshould also work:

find . -type d|awk 'NR>1{a[c++]=$0; t=t $0 SUBSEP} END{for (i in a) {if (index(t, a[i] "/") > 0) delete a[i]} for (i in a) print a[i]}'

Making it more readable:

find . -type d | awk 'NR > 1 {
   a[c++]=$0;
   t=t $0 SUBSEP
}
END {
   for (i in a) {
      if (index(t, a[i] "/") > 0)
         delete a[i]}
   for (i in a)
      print a[i]
}'

While it might look like more coding in this solution but in a big directory this awk based command should run much faster than the embedded find | wc solution, as in the question.

Performance Testing:

I ran it on a directory containing 15k+ nested sub directories and found this awk command considerably faster (250-300% faster) that the OP's find | wc command.

anubhava
  • 761,203
  • 64
  • 569
  • 643