Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have a directory "New Movies" containing 200+ subdirectories "Movie Name (year)". The subdirectories "Movie Name (year)" contains a single video file (avi/mkv) and several related jpg/xml files.

I need to move all directories containing a video file that is at least 180 days old to another directory.

For example:

New Movies/Movie A (year) would contain movie.mkv, folder.jpg & movie.xml and I want to move them to a subdirectory of the same name elsewhere with all files present and intact.

After looking elsewhere on this site I have tried:

cd "/mnt/user/New Movies/"
find -type f \( -name "*.avi" -or -name ".*mkv" \) -mtime +180 -exec sh -c 'mv "${0%/*}" /mnt/user/Movies' {} \;

The command successfully moves the first subdirectory and all files to their new home, however every single operation after this one returns the error

find: './Movie B (year)': No such file or directory
find: './Movie C (year)': No such file or directory

and so on through all directories contained under "New Movies" whether they were due to be moved or not.

share|improve this question

2 Answers 2

up vote 0 down vote accepted

Do not -exec mv the directory which is currently being examined by find. It seems that find gets confused when you do that.

Workaround: first find the directories, then move them.

cd "/mnt/user/New Movies/"
find -type f \( -name "*.avi" -or -name ".*mkv" \) -mtime +180 \
  -printf "%h\0" | xargs -0 mv -t /mnt/user/Movies

Explanation:

  • -printf prints the match according to the format string.
  • %h prints the path part of the match. This corresponds to the "${0%/*}" in your command.
  • \0 separates the items using the null character. This is just precaution in case the filenames contain newlines.
  • xargs collects the input from the pipe and then executes its arguments with the input appended. -0 tells xargs to expect the input to be null separated instead of newline separated.
  • mv -t target allows mv to be called with all the source arguments appended at the end.

Note that this is still not absolutely safe. Some freak scheduler timing in combination with pipe buffers might still cause the mv to be executed before find moved out of the directory. To prevent even that you can do it like this:

cd "/mnt/user/New Movies/"
find -type f \( -name "*.avi" -or -name ".*mkv" \) -mtime +180 \
  -printf "%h\0" > dirstomove
xargs -0 mv -t /mnt/user/Movies < dirstomove

Background explanation:

I asume what happens with your find is following:

  1. find traverses the directory /mnt/user/New Movies/. While there it takes note of the available directories in its cache.
  2. find traverses into one of the subdirectories using the system call chdir(subdirname).
  3. Inside find finds a movie file which passes the filters.
  4. find executes mv with the given parameters.
  5. mv moves the directory to /mnt/user/Movies.
  6. find goes back to parent directory using the system call chdir(..), which now points to /mnt/user/Movies instead of /mnt/user/New Movies/
  7. find is confused because it does not find the directories it noted earlier and throws up a lot of errors.

This assumption is based on the answer to this question: find -exec mv stops after first exec. I do not know why find just stops working in that case and throws up errors in your case. Different versions of find might be the explanation.

share|improve this answer

Set the IFS environmental variable to IFS=$(echo -en "\n\b"); so the shell will separate items based on newlines instead of spaces.

Additionally this -exec sh -c 'mv "${0%/*}" /mnt/user/Movies' {} \; is quite bizarre. If I'm not mistaken simplify to -exec mv {} /mnt/user/Movies \;

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.