How to loop over directories in Linux?

Viewed 293026

I am writing a script in bash on Linux and need to go through all subdirectory names in a given directory. How can I loop through these directories (and skip regular files)?

For example:
the given directory is /tmp/
it has the following subdirectories: /tmp/A, /tmp/B, /tmp/C

I want to retrieve A, B, C.

11 Answers

All answers so far use find, so here's one with just the shell. No need for external tools in your case:

for dir in /tmp/*/     # list directories in the form "/tmp/dirname/"
do
    dir=${dir%*/}      # remove the trailing "/"
    echo "${dir##*/}"    # print everything after the final "/"
done
cd /tmp
find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n'

A short explanation:

  • find finds files (quite obviously)

  • . is the current directory, which after the cd is /tmp (IMHO this is more flexible than having /tmp directly in the find command. You have only one place, the cd, to change, if you want more actions to take place in this folder)

  • -maxdepth 1 and -mindepth 1 make sure that find only looks in the current directory and doesn't include . itself in the result

  • -type d looks only for directories

  • -printf '%f\n prints only the found folder's name (plus a newline) for each hit.

Et voilĂ !

find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n"

If you want to execute multiple commands in a for loop, you can save the result of find with mapfile (bash >= 4) as a variable and go through the array with ${dirlist[@]}. It also works with directories containing spaces.

The find command is based on the answer by Boldewyn. Further information about the find command can be found there.

IFS=""
mapfile -t dirlist < <( find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n' )
for dir in ${dirlist[@]}; do
    echo ">${dir}<"
    # more commands can go here ...
done

a minimal bash loop you can build off of (based off ghostdog74 answer)

for dir in directory/*                                                     
do                                                                                 
  echo ${dir}                                                                  
done

to zip a whole bunch of files by directory

for dir in directory/*
do
  zip -r ${dir##*/} ${dir}
done                                               

TL;DR:

(cd /tmp; for d in */; do echo "${d%/}"; done)

Explanation.

There's no need to use external programs. What you need is a shell globbing pattern. To avoid the need of removing /tmp afterward, I'm running it in a subshell, which may or not be suitable for your purposes.

Shell globbing patterns in a nutshell:

  • * Match any non-empty string any number of times.
  • ? Match exactly one character.
  • [...] Matches with a character from between the brackets. You can also specify ranges ([a-z], [A-F0-9], etc.) or classes ([:digit:], [:alpha:], etc.).
  • [^...] Match one of the characters not between the braces.

* If no file names match the pattern, the shell will return the pattern unchanged. Any character or string that is not one of the above represents itself.


Consequently, the pattern */ will match any file name that ends with a /. A trailing / in a file name unambiguously identifies a directory.

The last bit is removing the trailing slash, which is achieved with the variable substitution ${var%PATTERN}, which removes the shortest matching pattern from the end of the string contained in var, and where PATTERN is any valid globbing pattern. So we write ${d%/}, meaning we want to remove the trailing slash from the string represented by d.

In short, put the results of find into an array and iterate the array and do what you want. Not the quickest but more organized thinking.

#!/bin/bash

cd /tmp

declare -a results=(`find -type d`)

#Iterate the results

for path in ${results[@]}
do
    echo "Your path is $path"
    #Do something with the path..

    if [[ $path =~ "/A" ]]; then
        echo $path | awk -F / '{print $NF}'
        #prints A

    elif [[ $path =~ "/B" ]]; then
        echo $path | awk -F / '{print $NF}'
        #Prints B

    elif [[ $path =~ "/C" ]]; then
        echo $path | awk -F / '{print $NF}'
        #Prints C
    fi
done

This can be reduced to find -type d | grep "/A" | awk -F / '{print $NF}' prints A

find -type d | grep "/B" | awk -F / '{print $NF}' prints B find -type d | grep "/C" | awk -F / '{print $NF}' prints C

Related