I am working on a script that needs to perform an action in every sub-directory of a specific folder.
What is the most efficient way to write that?
I am working on a script that needs to perform an action in every sub-directory of a specific folder.
What is the most efficient way to write that?
for D in $(find . -mindepth 1 -maxdepth 1 -type d); do
//Do whatever you need with D
done
find
params if you want recursive or non-recursive behavior. –
Coaptation find . -mindepth 1 -type d
–
Uriniferous mkdir 'directory name with spaces'
into four separate words. –
Ewe -mindepth 1 -maxdepth 1
or it went too deep. –
Butcherbird for D in `find . -type d`; do //Do whatever you need with D; done
–
Lody -depth 1
–
Iaea -depth Process each directory's contents before the directory itself.
I think it isn't a replace for mindepth
and maxdepth
. –
Eskilstuna find
I think one might as well get rid of the for loop and use find
with its exec
argument instead. –
Displode A version that avoids creating a sub-process:
for D in *; do
if [ -d "${D}" ]; then
echo "${D}" # your processing here
fi
done
Or, if your action is a single command, this is more concise:
for D in *; do [ -d "${D}" ] && my_command; done
Or an even more concise version (thanks @enzotib). Note that in this version each value of D
will have a trailing slash:
for D in */; do my_command; done
if
or [
with: for D in */; do
–
Foamy for D in *; do [ -d "${D}" ] && my_command; done
or a combination of the two latest: for D in */; do [ -d $D ] && my_command; done
–
Frigate for D in .* *; do
instead for D in *; do
. –
Travers for D in ./directory/*; do
–
Towhee ${D%/*}
instead of $D to trim the trailing slash. –
Whitewing ${D%/}
is probably safer so extra characters after the /
aren't accidentally removed. –
Metalinguistic for D in $(find . -mindepth 1 -maxdepth 1 -type d); do
//Do whatever you need with D
done
find
params if you want recursive or non-recursive behavior. –
Coaptation find . -mindepth 1 -type d
–
Uriniferous mkdir 'directory name with spaces'
into four separate words. –
Ewe -mindepth 1 -maxdepth 1
or it went too deep. –
Butcherbird for D in `find . -type d`; do //Do whatever you need with D; done
–
Lody -depth 1
–
Iaea -depth Process each directory's contents before the directory itself.
I think it isn't a replace for mindepth
and maxdepth
. –
Eskilstuna find
I think one might as well get rid of the for loop and use find
with its exec
argument instead. –
Displode The simplest non recursive way is:
for d in */; do
echo "$d"
done
The /
at the end tells, use directories only.
There is no need for
shopt -s dotglob
to include dotfiles/dotdirs when expanding wildcards. See also: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html –
Snifter /*
instead of */
with /
representing the path you want to use. –
Rootstock /*
would be for absolute path whereas */
would include the subdirectories from the current location –
Kelbee ${d%/*}
–
Whitewing find
command.In GNU find
, you can use -execdir
parameter:
find . -type d -execdir realpath "{}" ';'
or by using -exec
parameter:
find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;
or with xargs
command:
find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'
Or using for loop:
for d in */; { echo "$d"; }
For recursivity try extended globbing (**/
) instead (enable by: shopt -s extglob
).
For more examples, see: How to go to each directory and execute a command? at SO
-exec {} +
is POSIX-specified, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +
is another legal option, and invokes fewer shells than -exec sh -c '...' {} \;
. –
Ewe Handy one-liners
for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A
find * -type d ### Option B
Option A is correct for folders with spaces in between. Also, generally faster since it doesn't print each word in a folder name as a separate entity.
# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real 0m0.327s
user 0m0.084s
sys 0m0.236s
# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real 0m0.787s
user 0m0.484s
sys 0m0.308s
find . -type d -print0 | xargs -0 -n 1 my_command
my_command
. –
Hydrolyze This will create a subshell (which means that variable values will be lost when the while
loop exits):
find . -type d | while read -r dir
do
something
done
This won't:
while read -r dir
do
something
done < <(find . -type d)
Either one will work if there are spaces in directory names.
find ... -print0
and while IFS="" read -r -d $'\000' dir
–
Disaffection -d ''
is less misleading about bash syntax and capabilities, since -d $'\000'
implies (falsely) that $'\000'
is in some way different from ''
-- indeed, one could readily (and again, falsely) infer from it that bash supports Pascal-style strings (length-specified, able to contain NUL literals) rather than C strings (NUL delimited, unable to contain NULs). –
Ewe the accepted answer will break on white spaces if the directory names have them, and the preferred syntax is $()
for bash/ksh. Use GNU find
-exec
option with +;
eg
find .... -exec mycommand +;
#this is same as passing to xargs
or use a while loop
find .... | while read -r D
do
# use variable `D` or whatever variable name you defined instead here
done
find -exec
and passing to xargs
: find
will ignore the exit value of the command being executed, while xargs
will fail on a nonzero exit. Either might be correct, depending on your needs. –
Romie find ... -print0 | while IFS= read -r d
is safer -- supports names that begin or end in whitespace, and names that contain newline literals. –
Ewe You could try:
#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/
for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
cd "$f"
<your job here>
done
or similar...
Explanation:
find . -maxdepth 1 -mindepth 1 -type d
:
Only find directories with a maximum recursive depth of 1 (only the subdirectories of $1) and minimum depth of 1 (excludes current folder .
)
cd "$f"
. It doesn't work when the output from find
is string-split, so you'll have the separate pieces of the name as separate values in $f
, making how well you do or don't quote $f
's expansion moot. –
Ewe find
's output is line-oriented (one name to a line, in theory -- but see below) with the default -print
action. –
Ewe find -print
is not a safe way to pass arbitrary filenames, since one can run something mkdir -p $'foo\n/etc/passwd\nbar'
and get a directory that has /etc/passwd
as a separate line in its name. Handling names from files in /upload
or /tmp
directories without care is a great way to get privilege escalation attacks. –
Ewe find ... -exec
is safe: Arguments passed on an argv list are individually terminated by NULs). –
Ewe ( tempdir=$(mktemp -d "$PWD"/tempdir.XXXXXX) && cd "$tempdir" && { mkdir 'directory with spaces' $'directory\nwith\nnewlines'; for d in $(find . -type d -print); do echo "Found: $d"; done; }; rm -rf "$tempdir" )
and you'll see each word on its own separate line in the output, with its own Found:
prefix. –
Ewe if you want to perform an action INSIDE the folder and not ON folder.
Explanation: You have many pdfs and you would like to concetrate them inside a single folder. my folders
AV 001/
AV 002/
for D in *; do cd "$D";
# VERY
DANGEROUS COMMAND - DONT USE
#-- missing "", it will list files too. It can go up too.for d in */; do cd "$d"; echo $d; cd ..; done;
# works
succesfullyfor D in "$(ls -d */)"; do cd "$D"; done;
#
bash: cd: $'Athens Voice 001/\nAthens Voice 002/' - there is no such
folderfor D in "$(*/)"; do cd "$D"; done;
# bash: Athens
Voice 001/: is folderfor D in "$(`find . -type d`)"; do cd $D; done;
# bash: ./Athens: there is no such folder or filefor D in *; do if [ -d "${D}" ] then cd ${D}; done;
# many
arguments© 2022 - 2024 — McMap. All rights reserved.
mkdir 'foo * bar'
will causefoo
andbar
to be iterated over even if they don't exist, and the*
will be replaced with a list of all filenames, even non-directory ones). – Ewemkdir -p '/tmp/ /etc/passwd /'
-- if someone runs a script following this practice on/tmp
to, say, find directories to delete, they could end up deleting/etc/passwd
. – Ewe