With for loops
To iterate over the subdirectories of a directory, you can use a for
loop.
for dir in */; do
echo "Doing something in $dir"
done
The wildcard */
expands to subdirectories in the current directory, and to symbolic links to directories. Directories whose name begins with .
(i.e. directories that are dot files) are omitted. If you want to skip symbolic links, you can do so explicitly.
for dir in */; do
if [ -L "${dir%/}" ]; then continue; fi
echo "Doing something in $dir"
done
To execute a command on all the files in the directory, use the *
wildcard. Once again, this excludes dot files. If the directory is empty, the wildcard is not expanded.
for dir in */; do
if [ -L "${dir%/}" ]; then continue; fi
if (set -- "$dir"*; [ "$#" -ne 0 ]); then
somecommand -- "$dir"*
fi
done
If you need to call the command separately for each file, use a nested loop. In the following snippet, I use [ -e "$file" ] || [ -L "$file" ]
to test whether the file is an existing file (excluding broken symbolic links) or a symbolic link (broken or not); this condition only fails if "$file"
is an unexpanded wildcard pattern due to the directory being empty.
for dir in */; do
if [ -L "${dir%/}" ]; then continue; fi
for file in "$dir"*; do
if [ -e "$file" ] || [ -L "$file" ]; then
somecommand -- "$file"
fi
done
done
If the command needs to have the subdirectory as its current directory, there are two ways to do it. You can call cd
before and after.
for dir in */; do
if [ -L "${dir%/}" ]; then continue; fi
if cd -- "$dir"; then
for file in *; do
if [ -e "$file" ] || [ -L "$file" ]; then
somecommand -- "$file"
fi
done
cd ..
fi
done
This has the downside that it can fail in edge cases such as the subdirectory being moved during the command's execution or the process not having permission to change into parent directory. To avoid these edge cases, an alternative to cd ..
is to run the command in a subshell. A subshell duplicates the state of the original shell, including its current directory, and what happens in the subshell doesn't affect the original shell process.
for dir in */; do
if [ -L "${dir%/}" ]; then continue; fi
( cd -- "$dir" &&
for file in *; do
if [ -e "$file" ] || [ -L "$file" ]; then
somecommand -- "$file"
fi
done
)
fi
done
With find
As you can see, dealing with the non-nominal cases is not so easy. Some of these are edge cases that you may not be worried about, but an empty directory is something you should be able to deal with. There are easier ways to deal with those cases if you use ksh, bash or zsh; above I showed code that works in plain sh (and that doesn't deal with dot files).
Another way to enumerate files and directories is the find
command. It's designed to traverse directory trees recursively, but you can also use it even if there are no subsubdirectories. With GNU or FreeBSD find, i.e. on non-embedded Linux, Cygwin, FreeBSD or OSX, there's an easy way to execute a command on all the files in a directory tree:
find . ! -type d -execdir somecommand {} \;
This executes the command on all the files that aren't directories. To execute it only on regular files (excluding symbolic links and other special types of files):
find . -type f -execdir somecommand {} \;
If you want to exclude the files that are directly in the toplevel directory:
find . -mindepth 2 -type f -execdir somecommand {} \;
If there are subsubdirectories and you don't want to recurse into them:
find . -mindepth 2 -maxdepth 3 -type f -execdir somecommand {} \;