Use GNU find to show only the leaf directories

前端 未结 7 643
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-04 18:57

I\'m trying to use GNU find to find only the directories that contain no other directories, but may or may not contain regular files.

My best guess so far has been:

相关标签:
7条回答
  • 2020-12-04 19:36

    I just found another solution to this that works on both Linux & macOS (without find -exec)!

    It involves sort (twice) and awk:

    find dir -type d | sort -r | awk 'a!~"^"$0{a=$0;print}' | sort
    

    Explanation:

    1. sort the find output in reverse order

      • now you have subdirectories appear first, then their parents
    2. use awk to omit lines if the current line is a prefix of the previous line

      • (this command is from the answer here)
      • now you eliminated "all parent directories" (you're left with parent dirs)
    3. sort them (so it looks like the normal find output)
    4. Voila! Fast and portable.
    0 讨论(0)
  • 2020-12-04 19:39

    You can use -links if your filesystem is POSIX compliant (ie, a directory has a link for each subdirectory in it, a link from its parent and a link to self, thus a count of 2 link if it has no subdirectories).

    The following command should do what you want:

    find dir -type d -links 2
    

    However, it does not seems to work on Mac OS X (as @Piotr mentionned). Here is another version that is slower, but does work on Mac OS X. It is based on his version, with correction to handle whitespace in directory names:

    find . -type d -exec sh -c '(ls -p "{}"|grep />/dev/null)||echo "{}"' \;
    
    0 讨论(0)
  • 2020-12-04 19:42

    What about this one ? It's portable and it doesn't depend on finnicky linking counts. Note however that it's important to put root/folder without the trailing /.

    find root/folder -type d | awk '{ if (length($0)<length(prev) || substr($0,1,length(prev))!=prev) print prev; prev=($0 "/") } END { print prev }'
    
    0 讨论(0)
  • 2020-12-04 19:43

    @Sylvian solution didn't work for me on mac os x for some obscure reason. So I've came up with a bit more direct solution. Hope this will help someone:

    find . -type d  -print0 | xargs -0 -IXXX sh -c '(ls -p XXX | grep / >/dev/null) || echo XXX' ;
    

    Explanation:

    • ls -p ends directories with '/'
    • so (ls -p XXX | grep / >/dev/null) returns 0 if there is no directories
    • -print0 && -0 is to make xargs handle spaces in directory names
    0 讨论(0)
  • 2020-12-04 19:56

    I have some oddly named files in my directory trees that confuse awk as in @AhmetAlpBalkan 's answer. So I took a slightly different approach

      p=;
      while read c;
        do 
          l=${#c};
          f=${p:0:$l};
          if [ "$f" != "$c" ]; then 
            echo $c; 
          fi;
          p=$c; 
        done < <(find . -type d | sort -r) 
    

    As in the awk solution, I reverse sort. That way if the directory path is a subpath of the previous hit, you can easily discern this.

    Here p is my previous match, c is the current match, l is the length of the current match, f is the first l matching characters of the previous match. I only echo those hits that don't match the beginning of the previous match.

    The problem with the awk solution offered is that the matching of the beginning of the string seems to be confused if the path name contains things such as + in the name of some of the subdirectories. This caused awk to return a number of false positives for me.

    0 讨论(0)
  • 2020-12-04 19:56

    Here is solution which works on Linux and OS X:

    find . -type d -execdir bash -c '[ "$(find {} -mindepth 1 -type d)" ] || echo $PWD/{}' \; 
    

    or:

    find . -type d -execdir sh -c 'test -z "$(find "{}" -mindepth 1 -type d)" && echo $PWD/{}' \;
    
    0 讨论(0)
提交回复
热议问题