10

I have a bash script in which I need to iterate over each line of the ouput of the find command, but it appears that I am iterating over each Word (space delimited) from the find command. My script looks like this so far:

folders=`find -maxdepth 1 -type d`

for $i in $folders
do
    echo $i
done

I would expect this to give output like:

./dir1 and foo
./dir2 and bar
./dir3 and baz

But I am insted getting output like this:

./dir1
and
foo
./dir2
and
bar
./dir3
and
baz

What am I doing wrong here?

1

4 Answers 4

28
folders=`foo`

is always wrong, because it assumes that your directories won't contain spaces, newlines (yes, they're valid!), glob characters, etc. One robust approach (which requires the GNU extension -print0) follows:

while IFS='' read -r -d '' filename; do
  : # something with "$filename"
done < <(find . -maxdepth 1 -type d -print0)

Another safe and robust approach is to have find itself directly invoke your desired command:

find . -maxdepth 1 -type d -exec printf '%s\n' '{}' +

See the UsingFind wiki page for a complete treatment of the subject.

Sign up to request clarification or add additional context in comments.

3 Comments

Note that the find ... -exec form only works for normal commands -- if you want to use shell builtins, shell functions, compound commands, etc you need to use either the while ... done approach or the for loop chepner suggested.
@GordonDavisson Generally true. However, the UsingFind wiki page (linked in my answer) describes using -exec bash -c '...' _ {} +, which allows shell builtins and the like to be used.
Warning! Some commands, including ffmpeg, will read from stdin, and confuse the "read" command used in the loop. Symptoms are beginnings of the filename being cut off (=> invalid names). You need to disable this behaviour in the command that reads from stdin, two solutions here: stackoverflow.com/a/21634699
7

Since you aren't using any of the more advanced features of find, you can use a simple pattern to iterate over the subdirectories:

for i in ./*/; do
    echo "$i"
done

1 Comment

Most answers talk about using while. But, I was trying to find a solution that involved only a for loop and your solution works great. My scenario was to fetch filename from a different folder. So, added a small change to this : (cd $folder; for i in ./*/; do echo "$i"; done). Created a subshell, for running this on my shell without having to change folder paths
3

You can do something like this:

find -maxdepth 1 -type d | while read -r i
do
    echo "$i"
done

15 Comments

Unsafe. Doesn't correctly handle filenames containing backslash sequences, newlines, runs of multiple whitespace characters, etc.
@CharlesDuffy fixed and checked both ``, and whitespaces
...better -- with the -r you've handled backslash sequences, but you're still expanding globs, compressing runs of whitespace into a single character, and stripping trailing whitespace.
@CharlesDuffy how to check it? I've checked two whitespace, it handles that
Checked how? The problem you still have here is two spaces directly in a row, not two spaces anywhere in the filename.
|
0

Based on Charles Duffy's answer (kudos!), I made a bash function qfind (quoted find) which outputs all the output of find but quoted. I am adding it to my .bashrc

qfind() {
    while IFS='' read -r -d $'\0' findOutput; do
        /usr/bin/echo -n "${findOutput@Q} "
    done < <(/usr/bin/find "$@" -print0)
}

The quoted strings are separated with whitespace. ${findOutput@Q} expands to the quoted value of ${findOutput} like "$(quote "$findOutput")". When -n is removed from echo, it will also use newline characters for separation.

It can be used like this

eval "folders=($(qfind . -maxdepth 1 -type d))"
for i in "${folders[@]}"; do
    echo "$i"
done

eval is required to evaluate any quoted substrings.

With the predefined function, there won't be any additional commands in the loop body that read characters away from stdin during the loop.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.