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'
You can use the rename command (see edit 1).
Solution 1
For a reasonable number of files/directory, by setting bash 4 option globstar (not works on recursive name, see edit 3):
shopt -s globstar
rename -n 's/etckeeper/userkeeper/g' **
Solution 2
For a big number of files/directories using rename and find in two steps to prevent failed rename on files in just renamed directories (see edit 2):
find . -type f -exec rename 's/etckeeper/userkeeper/g' {} \;
find . -type d -exec rename 's/etckeeper/userkeeper/g' {} \;
EDIT 1:
There are two different rename commands.
This answer uses the Perl-based rename command, available in Debian-based distros. To have it on a not Debian based distro you can install from cpan or grab it around.
EDIT 2:
As pointed out by Jeff Schaller in the comments the -depth
find option Process each directory's contents before the directory itself
so only an "ordered" find by -depth
option would be enough:
find . -depth -exec rename 's/etckeeper/userkeeper/g' {} \;
EDIT 3
Solution 1 doesn't work for recursive rename targets, (es. etckeeper/etckeeper/etckeeper
) becasue outer levels are processed before inner levels and pointer to inner levels become useless. (After the first rename etckeeper/etckeeper/etckeeper
will be named usrkeeper/etckeeper/etckeeper
so the rename for etckeeper/etckeeper/
and etckeeper/etckeeper/etckeeper
will fail). The same problem fixed in find
by -depth
options.
EDIT4
As pointed out in the comments by cas, I'd use {} + rather than {} \; - forking a perl script like rename multiple times (once per file/dir) is expensive.
Best Answer
Warning: I typed most of these commands directly in my browser. Caveat lector.
With zsh and zmv:
Explanation: The pattern
**/*
matches all files in subdirectories of the current directory, recursively (it doesn't match files in the current directory, but these don't need to be renamed). The first two pairs of parentheses are groups that can be refered to as$1
and$2
in the replacement text. The final pair of parentheses adds theD
glob qualifier so that dot files are not omitted.-o -i
means to pass the-i
option tomv
so that you are prompted if an existing file would be overwritten.With only POSIX tools:
Explanation: the
case
statement omits the current directory and top-level subdirectories of the current directory.target
contains the source file name ($0
) with the leading./
stripped and all slashes replaced by dashes, plus a finalz
. The finalz
is there in case the filename ends with a newline: otherwise the command substitution would strip it.If your
find
doesn't support-exec … +
(OpenBSD, I'm looking at you):With bash (or ksh93), you don't need to call an external command to replace the slashes by dashes, you can use the ksh93 parameter expansion with string replacement construct
${VAR//STRING/REPLACEMENT}
: