11

given the following structure:

oz123@debian:~/ $ tree .
.
├── a
│   ├── a1
│   ├── a2
│   └── a3
├── a1
│   ├── a11
│   ├── a12
│   └── a31
├── b
│   └── b1
│       ├── b11
│       │   └── b21
│       │       └── b31
│       ├── b12
│       └── b3
└── c

16 directories, 0 files

How do I find all the end nodes?

I found the following solutions which seems to be good, but I have to proof that there is not test case which will fail it.

The help page of the -links states:

You can also search for files that have a certain number of links, with ‘-links’. Directories normally have at least two hard links; their . entry is the second one. If they have subdirectories, each of those also has a hard link called .. to its parent directory. The . and .. directory entries are not normally searched unless they are mentioned on the find command line.

possible solution:

oz123@debian:~/ $ find .  -type d  -links 2
./a/a2
./a/a3
./a/a1
./c
./a1/a31
./a1/a11
./a1/a12
./b/b1/b12
./b/b1/b3
./b/b1/b11/b21/b31
  • Can anyone provide a better solution (without using pipes and sed, this has be performant ...)
  • Will it work on any filesystem?
oz123
  • 517

4 Answers4

4

There is a bit more obvious option -empty:

find . -type d -empty

upd. Ok, you're right this way won't work with files in dirs.

So here it is a fixed file system undependable version:

find dtest/ -type d -exec sh -c "if [ \$(find {} -maxdepth 1 -type d | wc -l) -eq 1 ]; then echo {} ; fi" \;
rush
  • 27,403
3

As an addition to your own solution with -links, I want to just add that it will not work on filesystems that do not follow the Unix directory-link convention. From man find on option -noleaf these are at least CD-ROM, MS-DOS filesystems and AFS volume mount points.

For a reference, this question was already discussed with different solutions which are indeed slower and usually resort to piping to sed / awk and similar.

1

find . -type d -links 2 works on most filesystems, but not all. I don't think there's a way to know other than knowing which filesystem types have the property that directories contain a link to themselves. GNU find detects this dynamically (if it prints something out about “Automatically turning on find's -noleaf option”, you know your filesystem doesn't have this property). Most common filesystem types are ok, but not FAT or btrfs.

If you want to be sure, you'll have to test each directory. One way to do this is to invoke find again for each subdirectory.

find . -type d ! -exec sh -c '
   find "$1/." ! -name . -type d -prune | grep -q "^"' sh {} \; -print

(with GNU find, you can replace -prune with -print -quit to make it a bit more efficient).

Another way is to post-process the output of find. With find -depth, a leaf directory is one that does not follow a subdirectory of itself.

find . -depth -type d -print0 |
awk -v RS='\0' '
    substr(previous, 1, length($0) + 1) != $0 "/"
    { previous = $0 }
'
0

Try the following solution (should be Linux, Unix and OS X compatible):

find . -type d -execdir sh -c 'test -z "$(find "{}" -mindepth 1 -type d)" && echo $PWD/{}' ';'

It's similar approach to rush solution, but without any pipes.

kenorb
  • 20,988