Making BASH script `for` handle filenames with spaces (or workaround)

bashfilenamesfind

Whilst I have been using BASH for several years, my experience with BASH scripting is relatively limited.

My code is as below. It should grab the entire directory structure from within the current directory and replicate it into $OUTDIR.

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

The problem is, here is a sample of my file structure:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

Note the spaces :-S And for takes parameters word by word, so my script's output looks something like this:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

But I need it to grab whole filenames (one line at a time) from the output of find. I have also tried making find put double-quotes around each filename. But this doesn't help.

for DIR in `find . -type d -printf "\"%P\"\040"`

And output with this changed line:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

Now, I need some way that I can iterate through like this, because I also wish to run a more complicated command involving gstreamer on each file in a following similar structure. How should I be doing this?

Edit: I need a code structure which will allow me to run multiple lines of code for each directory/file/loop. Sorry if I was unclear.

Solution: I initially tried:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

This worked fine for the most part. However, I later found that since the pipe results in the while loop running in a subshell, any variables set in the loop were later unavailable which made implementing an error counter quite difficult. My final solution (from this answer on SO):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

This later allowed me to conditionally increment variables within the loop which would remain available later in the script.

Best Answer

You need to pipe the find into a while loop.

find ... | while read -r dir
do
    something with "$dir"
done

Also, you won't need to use -printf in this case.

You can make this proof against files with newlines in their names, if you wish, by using a nullbyte delimiter (that being the only character which cannot appear in a *nix filepath):

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

You will also find using $() instead of backticks to be more versatile and easier. They can be nested much more easily and quoting can be done much more easily. This contrived example will illustrate these points:

echo "$(echo "$(echo "hello")")"

Try to do that with backticks.

Related Question