Jonny
Jonny

Reputation: 3955

"mv: cannot stat" error when using find to move files via xargs

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

Answers (3)

Jonny
Jonny

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

Sanket Parmar
Sanket Parmar

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

alcala
alcala

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

Related Questions