As an alternative approach, you don't need external programs like rename
, basename
, etc - it can all be handled within bash
parameter expansion:-
find SourceDir ... | while read -r f; do mv "$f" "TargetDir/${f//\//_}"; done
The expansion is a bit difficult to follow, so here's what happens:-
- The files to be moved are found with
find
.
- Each file name is read in turn into
$f
.
- The
read -r
parameter and the double quotes around the expansions handle strange names, including spaces in the file names.
- The
mv
command moves names like a/b/c
to TargetDir/a_b_c
.
- The target expansion replaces every
/
by _
, but it looks daunting because /
is part of the substitution syntax.
- The general form is
${param/old/new}
, which replaces the first instance of old
by new
in the expansion.
- The form needed here is
${param//old/new}
, which replaces every instance of old
by new
in the expansion.
- In order for
/
to be part of the old string, it must be escaped as \/
, hence the rather obscure ${f//\//_}
: the first /
introduces the substitution syntax, the second /
specifies replace every, the third (escaped) /
is the old string, and the final /
introduces the new string (_
).
I don't often see this form of expansion in scripts, but it is worth knowing about, as it can be very useful at times.
There are some file names which will break this (embedded new-line characters, and leading or trailing spaces, though there are two ways round the latter:-
- Use
read -r
instead of read -r f
and use REPLY
instead of f
.
- Use
while f="$(line)"
instead of read -r f
.
The latter is neater, but uses the external program line
, which may not be available on all systems, though it can be coded as a function:
line() { read -r; r=$?; echo "$REPLY"; return $r; }
1Why not make a script? – var firstName – 2017-08-21T16:42:26.213
I did end up writing a (super inefficient) script (in nodejs.. it's what I know best) but was hoping to learn something new :) – Brad Dwyer – 2017-08-21T17:23:03.783