Rename files by reversing number order
With zsh
:
autoload zmv # best in ~/.zshrc
typeset -A c=()
zmv -n '(*)_<->.txt(#qnOn)' '$1_$((++c[${(b)1}])).txt-renamed' &&
: zmv '(*)-renamed' '$1'
(remove the -n
(dry-run) and :
, if happy (and remember to re-initialize c=()
before running again without dry run)).
<->
: is like<1-12>
to match decimal numbers in a range, but here with no bound specified, so matches any sequence of one or more decimal digits. Could also be written[0-9]##
where##
iszsh
's equivalent of ERE+
.(#q...)
is the explicit syntax for specifying glob qualifiers.n
: sorts numericallyOn
: sorts by name in reverse. So withn
above, that sorts the list of matching files numerically in reverse.- For the replacement,
$1
contains what's captured in(*)
, so the part before_<digits>.txt
. - We append
$((++c[${(b)1}]))
, where$c
is the associative array declared earlier. ${(b)1}
is$1
with glob characters escaped (without it, it wouldn't work properly if$1
contained]
).- we do it in 2 stages (append a
-renamed
suffix which is stripped in the second stage), to avoid overwriting files in the process.
On your sample, that gives:
mv -- data2_2.txt data2_1.txt-renamed
mv -- data2_1.txt data2_2.txt-renamed
mv -- data1_3.txt data1_1.txt-renamed
mv -- data1_2.txt data1_2.txt-renamed
mv -- data1_1.txt data1_3.txt-renamed
mv -- data1_1.txt-renamed data1_1.txt
mv -- data1_2.txt-renamed data1_2.txt
mv -- data1_3.txt-renamed data1_3.txt
mv -- data2_1.txt-renamed data2_1.txt
mv -- data2_2.txt-renamed data2_2.txt
Note that technically, it doesn't reverse the order, or only does it in the case where the numbers are incrementing by one and start at 1 like in your sample. It will turn all of [1, 2, 3]
, [4, 5, 6]
, [0, 10, 20]
to [3, 2, 1]
.
To reverse the list, it would be a bit more involved. It could be something like:
all_files=(*_<->.txt(n))
prefixes=(${all_files%_*})
for prefix (${(u)prefixes}) {
files=(${(M)all_files:#${prefix}_<->.txt})
new_files=(${(Oa)^files}-renamed)
for old new (${files:^new_files})
echo mv -i -- $old $new-renamed
}
(remove echo
when happy).
And run the zmv '(*)-renamed' '$1'
again as the second phase.
On a different sample with a additional [0, 3, 10, 20]
list as a third example, that gives:
mv -i -- data1_1.txt data1_3.txt-renamed
mv -i -- data1_2.txt data1_2.txt-renamed
mv -i -- data1_3.txt data1_1.txt-renamed
mv -i -- data2_1.txt data2_2.txt-renamed
mv -i -- data2_2.txt data2_1.txt-renamed
mv -i -- data3_0.txt data3_20.txt-renamed
mv -i -- data3_3.txt data3_10.txt-renamed
mv -i -- data3_10.txt data3_3.txt-renamed
mv -i -- data3_20.txt data3_0.txt-renamed
Those solutions make no assumption on what character (or non-character) the file names may contain, won't rename files unless they end in _<digits>.txt
. The zmv
-based approach will guard against overwriting files named with a -renamed
suffix that would have been there beforehand, not the latter approach (though -i
will cause mv
to prompt you before that happens). Alternatively, instead of adding a -renamed
suffix, you could move the renamed file into a renamed
directory.