mv: cannot stat No such file or directory in shell script
mv ${cwd}/${folder}'/data/*' ${cwd}/${folder}
The quotes ('
) there prevent the shell from doing globbing. The *
is being passed literally to the mv
command, which fails since it doesn't find files called *
in the directories indicated.
Change this to:
mv "${cwd}/${folder}/data"/* "${cwd}/${folder}"
(Double quotes to prevent problems if you ever have a directory name with spaces in it. *
outside the quotes.)
You'll still get the errors for the empty directories though. (Same sort of reason: if the file doesn't find a match for the pattern, it passes the pattern itself as an argument to the command.)
There are several problems with your code:
list=`ls dest_folder`
You're storing the output of ls
without the trailing newlines characters into $list
. ls
outputs the list of file names separated with newline characters. newline
is as valid a character as any in a filename, so that output cannot be used reliably. For instance the ls
output for a directory that contains a
and b
is the same as the one for a directory that contains one file called a<newline>b
.
cd dest_folder
You're not checking for failure of that command. In general you should check the exit status of commands, but that's especially true of cd
, because the rest of the commands assume you're in that new directory, and that could have dramatic consequences when you're not.
cwd=`pwd`
POSIX shells already maintain the (one) path to the current directory in the $PWD
variable, so you shouldn't need to use
here (especially in the general case since command substitution would remove trailing newline characters from the path). Also, pwd
mv
accepts relative paths so you don't need to build up the absolute path.
for folder in $list;do
Leaving a variable unquoted is the split+glob operator in shells. That is, the content of the variable is split (on the separators mentioned in $IFS
with special rules for the whitespace ones), and each element resulting of that splitting is looked for wildcard characters so they can be expanded to the list of matching files.
Here, you do want the splitting, but only on newline characters, and you don't want the globbing, so you'd need to disable it:
IFS='
'; set -f
for folder in $list
mv ${cwd}/${folder}'/data/*' ${cwd}/${folder}
Again, leaving a variable unquoted is the split+glob operator. Here, you want neither, so you need to quote those variables.
As already mentioned, wildcards are only expanded when not quoted, to you need to move that *
out of the quotes. If you've disabled globbing earlier with set -f
, you'd need to reinstate it with set +f
prior to calling that command.
A much better way to write it would be:
cd dest_folder &&
for folder in */;do
mv -- "${folder}data/"* "$folder"
done
A few notes though:
- That will exclude hidden folders and won't move hidden files from the
data
folders. - We're not checking for files being overridden in the process (you may want to add the
-i
option tomv
). - By using
*/
, we're looping over directories only, but that includes symbolic links to directories as well. If you'd rather not, you'd need to add a[ -L "${folder%/}" ] && continue
inside the loop. - If there's no non-hidden folder in there
*/
will expand to itself, so you will get and error message frommv
saying it can't find a file called*/data/*
. Similarly, if there's no non-hidden file in any of the folders, you'll get an error message thatthat-folder/data/*
doesn't exist.