16

I'm writing script is ksh. Need to find all directory names directly under the current directory which contain only files, not subdirectories.

I know that I could use ls -alR and recursively parse output for the first letter in the first field (d for a directory). I think awk is the best way to parse and find.

For example, a simple ls -al output in the current directory:

   drwxr-xr-x  22 af      staff    748 18 Mar 22:21 .
   drwxr-xr-x   5 root    admin    170 17 Mar 18:03 ..
   -rw-------   1 af      staff      3 17 Mar 16:37 .CFUserTextEncoding
   drwxr-xr-x   5 af      staff    170 17 Mar 17:12 Public
   drwxr-xr-x   9 af      staff    306 18 Mar 17:40 Sites
   -rw-------   1 af      staff      3 17 Mar 16:37 textd
   …

There are 2 directories in this output: Public and Sites. The directory Public doesn't contain subdirectories, but Sites does. There are 3 subdirectories in Sites. So I need to echo only the directories which don't contain directories in them. In my case, this is only Sites.

0

5 Answers 5

23

If you are able to use find and if you are working on a "normal Unix filesystem" (that is, as defined in find(1) under -noleaf option description), then the following command can be used:

find . -type d -links 2

Each directory has at least 2 names (hard links): . and its name. Its subdirectories, if any, will have a .. pointing to the parent directory, so a directory with N subdirectories will have hard link count equal to N+2. Thus, searching for directories with hard link count equal to 2, we search for directories with N=0 subdirectories.

So, if you can use find, this is arguably the fastest method and obviously superior to in-shell loops over the directory contents stat()'ing each of its members.

3
  • This doesn't seem to work if the directory contains any files.
    – user394
    Commented Sep 26, 2018 at 0:58
  • 1
    It's not about entries in the directory listing, it's about hard links referring to the directory's inode. For example, if /some/directory has inode #12345678, if it has no sub-directories, there will be exactly 2 hard links to that inode: /some/directory/. and /some/directory. It works whether or not there are any files in the directory.
    – telcoM
    Commented Jan 28, 2019 at 13:05
  • 4
    Worth mentioning that BTRFS is not a "normal Unix filesystem" in this respect: directories always have a link count of 1, unfortunately. (But . and foo/.. do have the same inode number.) Commented Jan 29, 2019 at 5:04
4

*/ matches the subdirectories of the current directory. This includes symbolic links to directories, which you may or may not desire.

In ksh93, adding ~(N) at the beginning of the pattern makes it expand to the empty list if there is no match. Without this, the pattern remains unchanged if there is no match.

The following ksh93 function lists the subdirectories of the current directories that do not contain any subdirectory or link to a directory.

list_leaf_directories () {
  local FIGNORE='.?(.)'        # don't ignore dot files
  local d
  for d in */; do
    [[ -L $d ]] || continue;   # skip symbolic links
    set -- ~(N)"$d"/*/
    if ((!$#)); then echo "$d"; fi
  done
done
3

You don't need to use awk at all. Use the built-in tests that ksh provides, something like this:

#!/bin/ksh

for NAME in *
do
    FOUND=no
    if [[ -d $NAME && $NAME != '.' && $NAME != '..' ]]
    then
        for SUBNAME in $NAME/*
        do
            if [[ -d $SUBNAME ]]
            then
                FOUND=yes
                break
            fi
        done
        if [[ $FOUND == no ]]
        then
            echo Found only files in $NAME
        fi
    fi
done

That little script looks in all the directories in the current directory, and tells you if they only contain files, no sub-directories.

0
1

Disclaimer: I am the current author of rawhide (rh) (see https://github.com/raforg/rawhide).

In How to find only directories without subdirectories? there is a solution that works on btrfs, but it's very complicated:

find . -type d \
    \( -exec sh -c 'find "$1" -mindepth 1 -maxdepth 1 -type d -print0 | grep -cz "^" >/dev/null 2>&1' _ {} \; -o -print \)

There's an alternative to find called rawhide (rh) that makes this much easier:

rh 'd && "[ $(rh -red -- %S | wc -l) = 0 ]".sh'

A slightly shorter/faster version is:

rh 'd && "[ -z \"$(rh -red -- %S)\" ]".sh'

The above commands search for directories and then list their sub-directories and only match when there are none (the first by counting the number of lines of output, and the second by checking if there is any output at all per directory).

If you don't need support for btrfs, it's more like find but still shorter:

rh 'd && nlink == 2'

For a version that works on all filesystems as efficiently as possible:

rh 'd && (nlink == 2 || nlink == 1 && "[ -z \"$(rh -red -- %S)\" ]".sh)'

On normal (non-btrfs) filesystems, this will work without the need for any additional processes for each directory, but on btrfs, it will need them. This is probably best if you have a mix of different filesystems including btrfs.

3
  • Looks interesting.  Can it use $(…) rather than `…`? Commented Sep 18, 2022 at 11:49
  • Yes. The "cmd".sh construct contains any arbitrary /bin/sh command. It is not limited. I just used `` because it's a character shorter.
    – raf
    Commented Sep 19, 2022 at 12:31
  • I've changed all the backticks to $(...) becsuse it's safer.
    – raf
    Commented Nov 17, 2023 at 6:20
0

if I am not misunderstanding you, you only want to find files in the directory not the subdirectories. If this is your intention, here is the solution

find . -type f

if you want to find other than regular files (like block device files, character device files etc) then see the man page for find command and look for the type keyword on that page. You will see different filetypes, including regular files and directories etc.

Hope this is what you are looking for

1
  • No, that's not the question at all. It's reasonably clear once you s/catalogue/directory/g. Nat Kup is looking for directories that don't contain subdirectories. Commented Mar 20, 2013 at 23:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .