Making BASH script `for` handle filenames with spaces (or workaround)
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
and get $2,000 discount on your first invoice
--------------------------------------------------
Music by Eric Matyas
https://www.soundimage.org
Track title: Sunrise at the Stream
--
Chapters
00:00 Making Bash Script `For` Handle Filenames With Spaces (Or Workaround)
02:08 Accepted Answer Score 12
02:52 Answer 2 Score 5
03:43 Answer 3 Score 8
04:20 Answer 4 Score 4
04:50 Thank you
--
Full question
https://superuser.com/questions/89570/ma...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#bash #find #filenames
#avk47
ACCEPTED ANSWER
Score 12
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.
ANSWER 2
Score 8
See this answer I wrote a few days ago for an example of a script that handles filenames with spaces.
There's a slightly more convoluted (but more concise) way to achieve what you're trying to do though:
find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}
-print0
tells find to separate the arguments with a null; the -0 to xargs tells it to expect arguments seperated by nulls. This means that it handles spaces fine.
-I {}
tells xargs to replace the string {}
with the filename. This also implies that only one filename should be used per commandline (xargs will normally stuff as many as will fit on the line)
The rest should be obvious.
ANSWER 3
Score 5
The issue you're encountering is the for statement is responding to the find as separate arguments. The space delimiter. You need to use bash's IFS variable to not split on space.
Here is a link that explains how to do this.
The IFS internal variable
One way around this problem is to change Bash's internal IFS (Internal Field Separator) variable so that it splits fields by something other than the default whitespace (space, tab, newline), in this case, a comma.
#!/bin/bash
IFS=$';'
for I in `find -type d -printf \"%P\"\;`
do
echo "== $I =="
done
Set your find to output your field delimiter after the %P and set your IFS appropriately. I picked semi-colon since it's highly unlikely to found in your filenames.
The other alternative is to call mkdir from the find directly via -exec
do you can skip the for loop altogether. That's if you don't need to do any additional parsing.
ANSWER 4
Score 4
If the body of your loop is more than a single command, it is possible to use xargs to drive a shell script:
export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
printf "mkdir -p %q\\n" "${OUTPATH}${DIR}" # Using echo for debug; working script will simply execute mkdir
echo Created $DIR
done' -
Be sure to include the trailing dash (or some other ‘word’) if the shell is of the Bourne/POSIX variety (it is used to set $0 in the shell script). Also, care must be taken with quoting, since the shell script is being written inside a quoted string instead of directly at the prompt.