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:When you run the
for
loop, bash will apply wordsplitting to what it reads, meaning thata strange blue cloud
will be read asa
,strange
,blue
andcloud
:Compare to:
Or even, if you insist on the UUoC:
So, the
while
loop will read over its input and use theread
to assign each line to a variable. TheIFS=
sets the input field separator to NULL*, and the-r
option ofread
stops it from interpreting backslash escapes (so that\t
is treated as slash +t
and not as a tab). The--
after themv
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.