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 ## is zsh's equivalent of ERE +.
(#q...) is the explicit syntax for specifying glob qualifiers.
n: sorts numerically
On: sorts by name in reverse. So with n 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.
one,twoandthree, doingmv three one; mv two two; mv one three, or you will live to regret it :-) And do it all on a copy of all the files.data1_13.txt?