How do I loop through only directories in bash?

I have a folder with some directory sites and also some files (some are surprise, starting with dot).

for d in *; do
 echo $d
done

will certainly loop via all files and also directory sites, yet I intend to loop just via directory sites. Just how do I do that?

454
2022-07-20 17:53:11
Source Share
Answers: 8

This will include the complete path in each directory in the list:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done
4
2022-07-25 21:53:59
Source

If you require to select even more details files than just directory sites make use of find and also pass it to while read:

shopt -s dotglob
find * -prune -type d | while IFS= read -r d; do 
    echo "$d"
done

Use shopt -u dotglob to exclude surprise directory sites (or setopt dotglob/ unsetopt dotglob in zsh).

IFS= to stay clear of splitting filenames having among the $IFS, as an example: 'a b'

see listed below for even more find alternatives


edit: ¢ In instance you require to create an exit value from within the while loop, you can prevent the added subshell by this trick:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)
35
2022-07-25 21:49:59
Source

Use find with -exec to loop via the directory sites and also call a function in the director option:

dosomething () {
  echo "doing something with $1"
}
export -f dosomething
find ./* -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Use shopt -s dotglob or shopt -u dotglob to include/exclude surprise directory sites

2
2022-07-25 21:49:04
Source

This is done to locate both noticeable and also surprise directory sites within the here and now functioning directory, leaving out the origin directory:

to simply loop via directories:

 find -path './*' -prune -type d

to include symlinks in the result:

find -L -path './*' -prune -type d

to do something per directory (leaving out symlinks) :

find -path './*' -prune -type d -print0 | xargs -0 <cmds>

to exclude surprise directories:

find -path './[^.]*' -prune -type d

to execute numerous commands on the returned values (a really contrived instance) :

find -path './[^.]*' -prune -type d -print0 | xargs -0 -I '{}' sh -c \
"printf 'first: %-40s' '{}'; printf 'second: %s\n' '{}'"

as opposed to 'sh - c' can additionally make use of 'bash - c', etc.

9
2022-07-25 20:57:46
Source

You can loop through all directories including hidden directories (beginning with a dot) in one line and multiple commands with:

for name in */ .*/ ; do printf '%s is a directory\n' "$name"; done

If you want to exclude symbolic links:

for name in *; do 
  if [ -d "$name" ] && [ ! -L "$name" ]; then
    printf '%s is a directory\n' "$name"
  fi 
done

Note: Using the list */ .*/ works in bash, but also displays the folders . and .. while in zsh it will not show these but throw an error if there is no hidden file in the folder


A cleaner version that will include hidden directories and exclude ../ will be with the dotglob shell option in bash:

shopt -s dotglob nullglob
for name in */ ; do printf '%s is a directory\n' "$name"; done

The nullglob shell option makes the pattern disappear completely (instead of remaining unexpanded) if no name matches it. (Use the pattern *(ND/) in the zsh shell; the / makes the preceding * match only directories, and the ND makes it act as if both nullglob and dotglob were set)

You may unset dotglob and nullglob with

shopt -u dotglob nullglob
4
2022-07-25 20:54:43
Source

You can make use of pure bash for that, yet it is far better to make use of find:

find . -maxdepth 1 -type d -exec echo {} \;

(locate in addition will include surprise directory sites)

15
2022-07-21 10:38:19
Source

You can examine with -d:

for f in *; do
    if [ -d "$f" ]; then
        # $f is a directory
    fi
done

This is just one of the file test operators.

120
2022-07-21 10:34:00
Source

You can define a lower at the end to match just directories:

for d in */ ; do
    echo "$d"
done

If you intend to exclude symlinks, make use of an examination to continue the loop if the existing access is a link. You require to remove the routing lower from the name in order for -L to be able to acknowledge it as a symbolic link:

for d in */ ; do
    [ -L "${d%/}" ] && continue
    echo "$d"
done
606
2022-07-21 10:32:52
Source