22

I have created the following script that move old days files as defined from source directory to destination directory. It is working perfectly.

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Files which were $days Days old moved from $soure to $destination"

This script moves files great, It also move files of source subdirectory, but it doesn't create subdirectory into destination directory. I want to implement this additional feature in it.

with example

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

When I run this script , it also move hex's files in maxi directory, but I need that same hex should be created into maxi directory and move its files in same hex there.

13

Instead of running mv /home/ketan/hex/foo /home/maxi, you'll need to vary the target directory based on the path produced by find. This is easier if you change to the source directory first and run find .. Now you can merely prepend the destination directory to each item produced by find. You'll need to run a shell in the find … -exec command to perform the concatenation, and to create the target directory if necessary.

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

Note that to avoid quoting issues if $destination contains special characters, you can't just substitute it inside the shell script. You can export it to the environment so that it reaches the inner shell, or you can pass it as an argument (that's what I did). You might save a bit of execution time by grouping sh calls:

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

Alternatively, in zsh, you can use the zmv function, and the . and m glob qualifiers to only match regular files in the right date range. You'll need to pass an alternate mv function that first creates the target directory if necessary.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'
  • for x do, you've got a missing ; there :). Also, I have no idea what you wanted to achieve with $0 but I'm quite convinced it would be sh :). – Michał Górny Oct 12 '14 at 19:02
  • @MichałGórny for x; do is technically not POSIX-compliant (check the grammar), but modern shells allow both for x do and for x; do; some old Bourne shells didn't grok for x; do. On modern shells, with sh -c '…' arg0 arg1 arg2 arg3, arg0 becomes $0, arg1 becomes $1, etc. If you want $0 to be sh, you need to write sh -c '…' sh arg1 arg2 arg3. Again, some Bourne shells behaved differently, but POSIX specifies this. – Gilles Oct 12 '14 at 19:11
  • pushd seems like a better choice that cd, as it less intrusive into the current environment. – jpmc26 Aug 15 '18 at 19:52
15

I know find was specified, but this sounds like a job for rsync.

I most often use the following:

rsync -axuv --delete-after --progress Source/ Target/

Here is a good example if you want to only move files of a particular file-type (example):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/
  • much cleaner than the others solutions :) – Guillaume May 31 '17 at 12:08
  • 2
    I found that --remove-source-files was helpful, which effectively causes files to be moved instead of copied. This is an awesome use of rsync. – Tom Aug 30 '17 at 5:27
3

you could do it using two instances of find(1)

There's always cpio(1)

(cd "$soure" && find … | cpio -pdVmu "$destination")

Check the arguments for cpio. The ones I gave

  • 1
    This will break on any filenames with whitespace. – Chris Down Dec 21 '12 at 17:26
  • 1
    This copies files instead of moving them. – Gilles Dec 22 '12 at 0:01
  • May not be a perfect answer, but it helped me to move files preserving paths in a more or less straight forward manner (I have enough space to copy the files then delete). Upvoted – AhHatem Apr 27 '14 at 10:01
3

It's not as efficient, but the code is easier to read and understand, in my opinion, if you just copy the files and then delete afterwards.

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

Notice: Flaw discovered by @MV for automated operations:

Using two separate operations is risky. If some files become older than 7 days while the copy operation is done, they won't be copied but they will be deleted by the delete operation. For something being done manually once this may not be an issue, but for automated scripts this may lead to data loss

  • 2
    Using two separate operations is risky. If some files become older than 7 days while the copy operation is done, they won't be copied but they will be deleted by the delete operation. For something being done manually once this may not be an issue, but for automated scripts this may lead to data loss. – MV. Dec 28 '16 at 5:47
  • 1
    The easy solution to this flaw is to run find once, save the list as a text file, and then use xargs twice to do the copy and then the delete. – David M. Perlman Feb 21 '18 at 19:48
1

You can do this by appending the absolute path of the file returned by find to your destination path:

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'
  • Chris now its moves whole home into maxi – Ketan Patel Dec 21 '12 at 15:11
  • @K.KPatel No, it doesn't. It merely keeps your directory structure. – Chris Down Dec 21 '12 at 17:26
  • This breaks if $destination contains special characters, because it undergoes expansion in the inner shell. Maybe you meant destination='\'"$destination"\''? That still breaks on '. Also, this creates files such as /home/maxi/home/ketan/hex/foo instead of /home/maxi/hex/foo. – Gilles Dec 22 '12 at 0:03
0

Better (fastest & without consuming storage space by doing copy instead of move), also is not affected by the file-names if they contain special characters in their names:

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

Or faster, moving a bunch of files in the same time for multi-CPU, using the "parallel" command:

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

P.S.: You have a typo, "soure" should be "source". I kept the variable name.

0

This is less elegant but easy if the number / size of files isn't too great

Zip your files together into a zip archive, and then unzip at the destination without the -j option. By default, zip will create the relative directory structure.

0

Try this way:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done
0

Because there seem to be no really easy solution to this and I need it very often, I created this open source utility for linux (requires python): https://github.com/benapetr/smv

There are multiple ways how you could use it to achieve what you need but probably most simple would be something like this:

 # -vf = verbose + force (doesn't stop on errors)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

You can additionally run it in dry mode so that it doesn't do anything but print what it would do

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

Or in case that list of files is too long to fit into argument line and you don't mind executing python for every single file, then

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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