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 ..
doneBut it just outputs the whole contents of my whole computer and goes back to root folder /~ ... any explanation for this behavior?
13 Answers
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 dIf 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
doneNote 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/* &
doneUse the & to run the code parallelised.
If you enable bash's globstar option with shopt -s globstar - which can be done either system-wide or on a case-by-case basis within a single terminal session or script - you can do the following:
shopt -s globstar
ls -d $PWD/** This will recursively travel through $PWD and all subdirectories.