232

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, but I want to loop only through directories. How do I do that?

12 Answers 12

297

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

for d in */ ; do
    echo "$d"
done
  • 6
    Note that it also includes symlinks to directories. – Stéphane Chazelas Aug 14 '13 at 16:09
  • 1
    so how would you exclude symlinks then? – rubo77 Aug 14 '13 at 22:55
  • 3
    @rubo77: You can test with [[ -L $d ]] whether $d is a symbolic link. – choroba Aug 14 '13 at 23:00
  • 1
    @AsymLabs That's incorrect, set -P only affects commands which change directory. sprunge.us/TNac – Chris Down Oct 22 '13 at 6:14
  • 3
    @choroba: [[ -L "$f" ]] will not exclude symlinks in this case with */, you have to strip the trailing slash with [[ -L "${f%/}" ]] (see Test for link with trailing slash) – rubo77 Oct 23 '13 at 6:54
70

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.

  • 7
    Note that it will include symlinks to directories. – Stéphane Chazelas Aug 14 '13 at 16:12
  • 18
    if [[ -d "$f" && ! -L "$f" ]] will exclude symlinks – rubo77 Oct 22 '13 at 6:33
  • Will break on empty directory. if [[ "$f" = "*" ]]; then continue; fi – Piskvor Apr 1 at 9:02
25

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.

  • 1
    Much easier to shopt -s nullglob. – choroba Jun 23 '16 at 7:14
19

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)
11

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)

  • 8
    Note that it doesn't include symlinks to directories. You can use shopt -s dotglob for bash to include hidden directories. Yours will also include .. Also note that -maxdepth is not a standard option (-prune is). – Stéphane Chazelas Aug 14 '13 at 16:11
  • 2
    The dotglob option is interesting but dotglob only applies to the use of *. find . will always include hidden directories (and the current dir as well) – rubo77 Oct 22 '13 at 5:28
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: %s\n' '{}'"

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

  • What happens if the echo '*/' in 'for d in echo */' contains, say, 60,000 directories? – AsymLabs Oct 21 '13 at 4:05
  • You will get "Too many files" error but that can be solved: How to circumvent “Too many open files” in debian – rubo77 Oct 21 '13 at 4:09
  • 1
    The xargs example only would works for a subset of directory names 9e.g. those with spaces or newlines would not work). Better to use ... -print0 | xargs -0 ... if you don't know what the exact names are. – Anthon Oct 21 '13 at 4:50
  • 1
    @rubo77 added way to exclude hidden files/directories and execution of multiple commands - of course this can be done with a script too. – AsymLabs Oct 21 '13 at 11:22
  • 1
    I added an example in my answer at the bottom, how to do something to each directory using a function with find * | while read file; do ... – rubo77 Oct 22 '13 at 4:37
3

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

for file in */ .*/ ; do echo "$file is a directory"; done

If you want to exclude symlinks:

for file in *; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    echo "$file is a directory"; 
  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 option:

shopt -s dotglob
for file in */ ; do echo "$file is a directory"; done

( or setopt dotglob in zsh )

you can unset dotglob with

shopt -u dotglob
2

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
  • 1
    breaks when using folders with spaces – Alejandro Sazo Oct 24 '16 at 2:15
0

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 -path './*' -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Use shopt -s dotglob or shopt -u dotglob to include/exclude hidden directories

0

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
-1
ls -d */ | while read d
do
        echo $d
done
  • This is one directory with spaces in the name - but gets parsed as multiple. – Piskvor Mar 28 at 11:35
-4
ls -l | grep ^d

or:

ll | grep ^d

You may set it as an alias

  • 1
    Unfortunately, I don't think this answers the question, which was "I want to loop only through directories" -- which is slightly different than this ls-based answer, which lists directories. – Jeff Schaller Sep 11 '17 at 20:26
  • 1
    It is never a good idea to parse the output of ls: mywiki.wooledge.org/ParsingLs – codeforester Aug 21 '18 at 18:38

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.