A safe, but not very fast way would be to use find
with -exec
, passing each file to a shell command where you perform the replacements, like this:
(cd /path; find . -type f -exec sh -c 'newname=${1:2}; filename=${newname##*/}; dirname=${newname%/*}; newname=$filename-${dirname//\//-}; echo mv "$1" "$newname"' -- {} \;)
The reason for the cd /path
instead of find /path
is to so that we can work with relative paths from that base directory, which makes it easier to create the flattened file names.
A bit more explanation of the elements:
-exec sh -c '...' -- {}
executes sh
, running the commands specified in -c '...'
. The {}
is the path found by find
. The -- ...
syntax is to pass parameters to the script as positional arguments, accessible via $1
, $2
, and so on.
newname=${1:2}
takes $1
, the first positional argument, and extracts a sub-string from it, skipping the first two characters. I do this because the output of find .
will have paths starting with ./
, and we want to remove that before replacing the /
with -
.
filename=${newname##*/}
extracts the filename part of the path, by removing everything from the beginning until the last /
dirname=${newname%/*}
extracts the directory name part of the path, by removing the last /
and everything after it
${dirname//\//-}
replaces all occurrences of /
with -
. If you want to remove the /
instead of replacing, then you can write ${dirname//\//}
This technique is safe, because it will work with any special characters in filenames. It's slow because it runs sh
for each file.
After this step the empty directories of the original files will remain, and you probably want to remove them:
find /path -type d -empty -delete