How do I loop through only directories in bash?

I have a folder with some directories and some files (some are hidden, beginning with dot).

for d in *; do
 echo $d
done

will loop through all files and directories, but I want to loop only through directories. How do I do that?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

You can specify a slash at the end to match only directories:

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

If you want to exclude symlinks, use a test to continue the loop if the current entry is a link. You need to remove the trailing slash from the name in order for -L to be able to recognise it as a symbolic link:

for d in */ ; do
    [ -L "${d%/}" ] && continue
    echo "$d"
done

Method 2

You can test with -d:

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

This is one of the file test operators.

Method 3

Beware that choroba’s solution, though elegant, can elicit unexpected behavior if no directories are available within the current directory. In this state, rather than skipping the for loop, bash will run the loop exactly once where d is equal to */:

#!/usr/bin/env bash

for d in */; do
    # Will print */ if no directories are available
    echo "$d"
done

I recommend using the following to protect against this case:

#!/usr/bin/env bash

for f in *; do
    if [ -d "$f" ]; then
        # Will not run if no directories are available
        echo "$f"
    fi
done

This code will loop through all files in the current directory, check if f is a directory, then echo f if the condition returns true. If f is equal to */, echo "$f" will not execute.

Method 4

If you need to select more specific files than only directories use find and 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 hidden directories (or setopt dotglob/unsetopt dotglob in zsh).

IFS= to avoid splitting filenames containing one of the $IFS, for example: 'a b'

see AsymLabs answer below for more find options


edit:
In case you need to create an exit value from within the while loop, you can circumvent the extra subshell by this trick:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)

Method 5

You can use pure bash for that, but it’s better to use find:

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

(find additionally will include hidden directories)

Method 6

This is done to find both visible and hidden directories within the present working directory, excluding the root directory:

to just loop through directories:

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

to include symlinks in the result:

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

to do something to each directory (excluding symlinks):

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

to exclude hidden directories:

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

to execute multiple commands on the returned values (a very contrived example):

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

instead of ‘sh -c’ can also use ‘bash -c’, etc.

Method 7

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 directoryn' "$name"; done

If you want to exclude symbolic links:

for name in *; do 
  if [ -d "$name" ] && [ ! -L "$name" ]; then
    printf '%s is a directoryn' "$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 directoryn' "$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

Method 8

Use find with -exec to loop through the directories and call a function in the exec 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 hidden directories

Method 9

This answer is assuming that all that needs to be done is to find the sub-directories of some top-level directory.

For an answer specific to bash (and to some degree, the zsh shell), see the second part of rubo77’s wiki answer, where dotglob and nullglob is being used to correctly loop over the directories (or symbolic links to directories) in a single directory.

For a portable solution that does not depend on bash for doing the actual selection of files, you may use find to loop over all directories in some top-level directory like so:

topdir=.

find "$topdir" ! -path "$topdir" -prune -type d -exec sh -c '
    for dirpath do
        # user code goes here, using "$dirpath"
        printf ""%s" is a directoryn" "$dirpath"
    done' sh {} +

The above code assumes that the top directory that we’re interested in is the current directory (.). Set topdir to some other pathname to use it on another directory.

The find utility is asked to ignore pathnames that are identical to the starting search path with ! -path "$topdir", and then to prune (not enter) any other pathnames found. This limits the search to only the directory referenced by $topdir.

Pathnames that are directories (-type d) are collected and a sh -c script is executed with batches of these as arguments. The sh -c script is the code that we’d like to execute on all the directories, so it iterates over its given arguments and does whatever it needs to do with each of them. If you need to do something that requires bash, you would obviously use bash -c instead.

The sh -c script could be separated out into its own script like so:

#!/bin/sh

for dirpath do
    # user code goes here, using "$dirpath"
    printf '"%s" is a directoryn' "$dirpath"
done

… which would be called from find like so:

find "$topdir" ! -path "$topdir" -prune -type d -exec ./myscript.sh {} +

See also:

Method 10

Lists only directories. inclusive with spaces in names include hidden directory -A if needed:

grep '/$' <(ls -AF)

Possible sequels:

| xargs -I{} echo {}
| while read; do echo $REPLY; done

But judging by the tricky question, it meant the output of directories in the for loop and by means of the shell:

for i in */ .[^.]*/; do
        [ -h "${i%?}" ] || echo $i
done

Method 11

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

Method 12

This lists all the directories together with the number of sub-directories in a given path:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done

Method 13

ls -d */ | while read d
do
        echo $d
done

Method 14

ls -l | grep ^d

or:

ll | grep ^d

You may set it as an alias


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x