Reputation: 3955
I'm working on a bash script that needs to (recursively) move all files and folders within a source folder into a destination folder.
In trying to make this as robust as possible, and address potential argument list too long - errors, I opted to use the find
command (to safely determine the files to move) piped into xargs
(to efficiently group the moves together). I'm also using -print0
and -0
to address potential problems with spaces.
I've written the following test script:
#!/bin/bash
# create test source file structure, and destination folder
mkdir -p sourceDir/{subdir1,subdir\ with\ space\ 2}
mkdir -p destDir
touch sourceDir/subdir1/{a1.txt,noExtension1,file1\ with\ space.txt}
touch sourceDir/subdir\ with\ space\ 2/{a2.txt,noExtension2,file2\ with\ space.txt}
#move files/folders from source to destination
find sourceDir -mindepth 1 -print0 | xargs -0 mv --target-directory=destDir
Which seems to work (i.e. the files are moved) but for some reason I get numerous errors as follows:
mv: cannot stat `sourceDir/subdir with space 2/file2 with space.txt': No such file or directory
mv: cannot stat `sourceDir/subdir with space 2/noExtension2': No such file or directory
mv: cannot stat `sourceDir/subdir with space 2/a2.txt': No such file or directory
mv: cannot stat `sourceDir/subdir1/file1 with space.txt': No such file or directory
mv: cannot stat `sourceDir/subdir1/noExtension1': No such file or directory
mv: cannot stat `sourceDir/subdir1/a1.txt': No such file or directory
Should there not be a way to do this (for the source files indicated in my script) without generating errors?
Why are these errors being generated (since the files and folders are in fact being moved)?
Upvotes: 1
Views: 6402
Reputation: 3955
I've sussed it.
Short answer: I was missing the -maxdepth 1
tag. The following command works
find sourceDir -mindepth 1 -maxdepth 1 -print0 | xargs -0 mv --target-directory=destDir
# as does
find sourceDir -mindepth 1 -maxdepth 1 -exec mv --target-directory=destDir '{}' +
Longer answer:
My original find command was listing the paths to each and every file, and then trying to move each and every one of them. Once a top level folder had been moved, there was then no need to move any files/folders underneath - but the script was still trying to!
The script below demonstrates this (without actually performing the moves):
#!/bin/bash
mkdir -p sourceDir/{subdir1/subsubdir1,subdir\ with\ space\ 2}
touch sourceDir/subdir1/{subsubdir1/deeperFile.log,a1.txt,noExtension1,file1\ with\ space.txt}
touch sourceDir/subdir\ with\ space\ 2/{a2.txt,noExtension2,file2\ with\ space.txt}
touch sourceDir/rootFile.txt
echo -e "\n--- Output of 'ls -1: sourceDir':"
echo "$(ls -1 sourceDir)"
echo -e "\n--- original incorrect attempt was trying to move each of the following:"
find sourceDir -mindepth 1
echo -e "\n--- working attempt only needs to move each of the top level files/folders, everything underneath any folders will get moved automatically"
find sourceDir -mindepth 1 -maxdepth 1
giving output:
--- Output of 'ls -1: sourceDir':
rootFile.txt
subdir1
subdir with space 2
--- original incorrect attempt was trying to move each of the following:
sourceDir/rootFile.txt
sourceDir/subdir with space 2
sourceDir/subdir with space 2/file2 with space.txt
sourceDir/subdir with space 2/noExtension2
sourceDir/subdir with space 2/a2.txt
sourceDir/subdir1
sourceDir/subdir1/file1 with space.txt
sourceDir/subdir1/noExtension1
sourceDir/subdir1/a1.txt
sourceDir/subdir1/subsubdir1
sourceDir/subdir1/subsubdir1/deeperFile.log
--- working attempt only needs to move each of the top level files/folders, everything underneath any folders will get moved automatically
sourceDir/rootFile.txt
sourceDir/subdir with space 2
sourceDir/subdir1
Upvotes: 1
Reputation: 1577
By default the output of find command starts with parent directory, then directory contain.
Simple find command on your directroy structure gives following output:
sourceDir/subdir1
sourceDir/subdir1/a1.txt
sourceDir/subdir1/noExtension1
sourceDir/subdir1/file1 with space.txt
sourceDir/subdir with space 2
sourceDir/subdir with space 2/noExtension2
sourceDir/subdir with space 2/file2 with space.txt
If you use this output in xargs
or -exec
, It will first process the parent directory and after that its contents. In this case your command executes fine and move all whole sourceDir/subdir1 directory to destDir/subdire1. After that there is no files/directory like sourceDir/subdir1/a1.txt or sourceDir/subdir1/noExtension1 in sourceDir because it is already moved to destDir.
To avoid this use -depth
option.
-depth Process each directory's contents before the directory itself.
In your case you can add -type d
option to move top level source dir to destDir.
find sourceDir -mindepth 1 -type d -print0 | xargs -0 mv --target-directory=destDir
Or
find sourceDir -mindepth 1 -type d -exec mv -t destDir "{}" \+
It will solve your problem.
Upvotes: 0
Reputation: 1153
Why don't do that directly with the find
command ?
$ find [...] -exec mv '{}' [...] ';'
Where {}
means each file found and ;
the end of the arguments passed to mv
Upvotes: 0