Bash: moving files with spaces

bashfilesmv

When I move a single file with spaces in the filename it works like this:

$ mv "file with spaces.txt" "new_place/file with spaces.txt"

Now I have a list of files which may contain spaces and I want to move them. For example:

$ echo "file with spaces.txt" > file_list.txt
$ for file in $(cat file_list.txt); do mv "$file" "new_place/$file"; done;

mv: cannot stat 'file': No such file or directory
mv: cannot stat 'with': No such file or directory
mv: cannot stat 'spaces.txt': No such file or directory

Why does the first example work, but the second one doese not? How can I make it work?

Best Answer

Never, ever use for foo in $(cat bar). This is a classic mistake, commonly known as bash pitfall number 1. You should instead use:

while IFS= read -r file; do mv -- "$file" "new_place/$file"; done < file_list.txt

When you run the for loop, bash will apply wordsplitting to what it reads, meaning that a strange blue cloud will be read as a, strange, blue and cloud:

$ cat files 
a strange blue cloud.txt
$ for file in $(cat files); do echo "$file"; done
a
strange
blue
cloud.txt

Compare to:

$ while IFS= read -r file; do echo "$file"; done < files 
a strange blue cloud.txt

Or even, if you insist on the UUoC:

$ cat files | while IFS= read -r file; do echo "$file"; done
a strange blue cloud.txt

So, the while loop will read over its input and use the read to assign each line to a variable. The IFS= sets the input field separator to NULL*, and the -r option of read stops it from interpreting backslash escapes (so that \t is treated as slash + t and not as a tab). The -- after the mv means "treat everything after the -- as an argument and not an option", which lets you deal with file names starting with - correctly.


* This isn't necessary here, strictly speaking, the only benefit in this scenario is that keeps read from removing any leading or trailing whitespace, but it is a good habit to get into for when you need to deal with filenames containing newline characters, or in general, when you need to be able to deal with arbitrary file names.

Related Question