1

I would like a solution that works on Linux and is portable across shells (not just bash) and filesystems (including drvfs or btrfs) NB: directory names may contain spaces

With find I can produce a list of all paths rooted within a folder like this:

find -type d
.
./a dir
./a dir/20210101
./a dir/20210101/bin
./a dir/20210101/etc
./a dir/20210101/var
./a dir/20210101/var/log
./a dir/20211201
./b dir
./b dir/20210212
./b dir/20210212/bin
./b dir/20210212/etc
./c dir
./d dir
./d dir/20210711

I would however like to exclude "base" or "parent" paths that are already included in the the deepest unique path. Please also help with the correct terms to use to describe this as I feel I am not using the optimal description.

I can do it with a basic script but assume there is a more elegant way using one of the following:

  • find
  • ls

Here is my script:

save_ifs=$IFS;
IFS=$'\n';
prev_path="";
for path in $(find -depth -type d); do

    if [ ! ${#path} -lt ${#prev_path} ]; then
        echo $path;
    fi
    
    prev_path=$path;

done

and its output - which is the desired output

./a dir/20210101/bin
./a dir/20210101/etc
./a dir/20210101/var/log
./a dir/20211201
./b dir/20210212/bin
./b dir/20210212/etc
./c dir
./d dir/20210711
5
  • 3
    Does this answer your question? Find directories that do not contain subdirectories Also: unix.stackexchange.com/questions/497185/…
    – muru
    Sep 9 '21 at 10:49
  • @muru the questions you referenced could have worked on a filesystem supporting that. I see that I am testing on the drvfs filesystem which does not support link(2). I will update my question to reflect this. Sep 9 '21 at 14:12
  • Also clarify if your directory names can contain newline characters as well as spaces or if we can safely ignore that edge case.
    – terdon
    Sep 9 '21 at 14:24
  • @Justchoose1, yep, I thought so, but was afraid it was hiding something that matter with the problem
    – ilkkachu
    Sep 10 '21 at 9:59
  • Is it acceptable to change the question title. Wouldn't it be better to use: What is the best way to output (in any shell) a list of relative paths to all the leaf directories on a non-POSIX compliant system? Sep 11 '21 at 13:15
0

This looks like the opposite variant of Filter out paths from a text file that are deeper than their immediate predeccessor.

So, reworking my answer there, here's one option:

tac input.txt | awk '
   { sub(/\/?$/, "/") }
   NR == 1 || substr(prev, 0, length($0)) != $0 { print }
   { prev = $0 }; ' | tac | sed -e 's/\/$//'

or

cat input.txt | awk '
    { sub(/\/?$/, "/") }
    NR != 1 && substr($0, 0, length(prev)) != prev {print prev}
    { prev = $0 }
    END { print }; ' | sed -e 's/\/$//'

With the input in your first listing, both give:

./a dir/20210101/bin
./a dir/20210101/etc
./a dir/20210101/var/log
./a dir/20211201
./b dir/20210212/bin
./b dir/20210212/etc
./c dir
./d dir

The assumption is that the input is ordered so that the contents of a directory come immediately after that directory (i.e. depth-first, preordered), which is what the output of find should be like(*) and which matches what lexicographic sorting in the C locale would give.

(Unless you use -depth, which unlike the name implies, would switch to postordering, but would still be a depth-first search.)

0

EDIT: Rephrased reply, code is unchanged.

The default depth-first search by find . -type d outputs parent just before child, if any, so skip lines immediately followed by a child dir (one appending a subdir). Here's a one-liner with a POSIX script. Assumes pathnames without newlines.

find . -type d | sed -e '$!N' -e '/\(.*\)\n\1\//!P' -e 'D'

where:

  • the standard N; …; P; D; command sequence opens a sliding two-line window into input
  • unless at last line ($!) append \n+next line to pattern space (N)
  • if second line does not match first line followed by / (\1\/) print the first (P)
  • delete first line and resume loop (D)
0

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.