Loop through directories and subdirectories in bash

I tried with this line, starting at the parent folder containing the folders I want to loop through:

for dir in */; do  
    cd $dir  
    for dir2 in */; do  
        cd $dir2  
        ls -d $PWD/*  
        cd ..  
    done  
    cd ..  
done

But it just outputs the whole contents of my whole computer and goes back to root folder /~ ... any explanation for this behavior?


bash's default "globbing" (wildcard matching/expansion) behavior is to return the wildcard expression itself if the wildcard doesn't match anything. So if you cd into a directory that has no subdirectories, you'll end up trying to cd into literally '*/' (literally a directory whose name is an asterisk), and that will fail, so you'll stay one directory higher than you thought you'd be at this point in the loop. So when you then cd .., you'll be one directory higher again. Eventually you'll back yourself out to the root level of your filesystem.

Many shell scripters choose to use find(1) rather than shell loops when they want to have a script walk a directory tree. Your whole script snippet would become:

find . -type d

…or, if you really wanted absolute paths rather than relative paths:

find "$PWD" -type d

If you want to stick with shell loops for this, you might try something like this:

#!/bin/bash
set -evx
for dir in */; do
    if [ -d "$dir" ]; then
        cd "$dir"
        for dir2 in */; do
            if [ -d "$dir2" ]; then
                cd "$dir2"
                ls -d "$PWD"/*
                cd ..
            fi
        done
        cd ..
    fi
done

Note how I've used if statements to test each value of dir and dir2 to make sure they are truly directories, and I skip them if they are not.

Also note the set -evx line, which are a nice set of options to set when you're first writing/debugging a script:
-e stops on the first error.
-v prints each line before it executes it.
-x prints each line after all the various substitutions/expansions have been performed, before executing it.

Also consider spending some time in the bash(1) man page looking at shell options like nullglob and failglob, if you don't like the default globbing behavior of returning the wildcard expression itself when it fails to match anything.


You can simply use multiple asterixes:

for dir in */*; do 
    ls -d $PWD/* &
done

Use the & to run the code parallelised.