С помощью команды GNU sed R
:
sed -e $'R fileb\nR fileb\nR fileb' filea > output
Сzsh
:
autoload zmv # best in ~/.zshrc
typeset -A c=()
zmv -n '(*)_<->.txt(#qnOn)' '$1_$((++c[${(b)1}])).txt-renamed' &&
: zmv '(*)-renamed' '$1'
(удалите-n
(пробный прогон -)и :
, если все устраивает (и не забудьте повторно -инициализировать c=()
перед повторным запуском без пробного прогона )).
<->
:похоже на <1-12>
для соответствия десятичным числам в диапазоне, но здесь без указания границы, поэтому соответствует любой последовательности из одной или нескольких десятичных цифр. Также можно написать [0-9]##
, где ##
— эквивалент zsh
для ERE +
. (#q...)
— это явный синтаксис для указания квалификаторов glob . n
:сортирует по номерам On
:сортирует по имени в обратном порядке. Так что с n
выше, это сортирует список совпадающих файлов в обратном порядке. $1
содержит то, что захвачено в (*)
, поэтому часть перед _<digits>.txt
. $((++c[${(b)1}]))
, где $c
— объявленный ранее ассоциативный массив. ${(b)1}
равно $1
с экранированными символами глобуса (без него, это не будет работать должным образом, если $1
содержит]
). -renamed
, который удаляется на втором этапе ), чтобы избежать перезаписи файлов в процессе. На вашем образце это дает:
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
Обратите внимание, что технически это не меняет порядок на обратный или делает это только в том случае, когда числа увеличиваются на единицу и начинаются с 1, как в вашем образце. Он превратит все [1, 2, 3]
, [4, 5, 6]
, [0, 10, 20]
в [3, 2, 1]
.
Чтобы перевернуть список, это было бы немного сложнее. Это может быть что-то вроде:
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
}
(удалить echo
когда счастлив ).
И снова запустите zmv '(*)-renamed' '$1'
как вторую фазу.
В другом образце с дополнительным списком [0, 3, 10, 20]
в качестве третьего примера, который дает:
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
Эти решения не предполагают, какой символ (или не -символ )могут содержать имена файлов, не будут переименовывать файлы, если они не заканчиваются на _<digits>.txt
. Подход, основанный на zmv
-, защитит от перезаписи файлов, названных с суффиксом -renamed
, который был бы там заранее, а не последний подход (, хотя -i
заставит mv
подсказать вам, прежде чем это произойдет ). Кроме того, вместо добавления суффикса -renamed
вы можете переместить переименованный файл в каталог renamed
.
Вот простое решение, и я думаю, что оно отлично подойдет для вашей ситуации. Здесь мы сначала проверяем, сколько типов файлов мы получили по префиксу. Здесь под префиксом типа файла я имел в виду data1 _, data2_и так далее. Затем для каждого типа префикса мы получаем общее количество доступных файлов и сохраняем их в массиве с именем totalFilesForEachPrefix
.
Затем на шаге 1 мы переименовываем файл во временный. Причина перехода на расширение otemp
— избежать конфликта имен и перезаписать существующий файл. Здесь я предположил, что у вас есть файлы типа 1 _1 1 _2 1 _3 1 _4 и так далее.
Затем, на шаге 2, мы просто избавляемся от .otemp
расширения.
#!/usr/bin/env bash
totalFileTypeByPrefix=$( for file in data*_1.txt; do echo $file; done | wc -l )
totalFilesForEachPrefix=()
for (( prefix = 1; prefix <= totalFileTypeByPrefix; prefix++ )); do
totalFilesForEachPrefix+=( $( for file in data${prefix}_*.txt; do echo $file; done | wc -l) )
done
### Step 1
prefix=1; type=0;
while (( prefix <= totalFileTypeByPrefix )); do
suffix=${totalFilesForEachPrefix[$type]}
for file in data${prefix}_*.txt; do
mv $file data${prefix}_${suffix}.txt.otemp; echo "$file renamed temporary --> data${prefix}_${suffix}.txt.otemp"
suffix=$((suffix -1))
done
type=$((type+1))
prefix=$((prefix+1))
done
### Step 2
echo "....Finally changing the temporary files"....
for tempfile in *.otemp; do
file=${tempfile::-6};
mv $tempfile $file; echo "$tempfile renamed final --> $file";
done
Вот вывод с объяснением того, что происходит:
data1_1.txt renamed temporary --> data1_3.txt.otemp
data1_2.txt renamed temporary --> data1_2.txt.otemp
data1_3.txt renamed temporary --> data1_1.txt.otemp
data2_1.txt renamed temporary --> data2_2.txt.otemp
data2_2.txt renamed temporary --> data2_1.txt.otemp
....Finally changing the temporary files....
data1_1.txt.otemp renamed final --> data1_1.txt
data1_2.txt.otemp renamed final --> data1_2.txt
data1_3.txt.otemp renamed final --> data1_3.txt
data2_1.txt.otemp renamed final --> data2_1.txt
data2_2.txt.otemp renamed final --> data2_2.txt
Глупый, но всегда работающий метод (следующий Принципу KISS)заключается в следующем:
/bin/ls data*.txt >rename.sh
# edit the file so that instead of
# data1_1.txt
# each line reads
# mv data1_1.txt data1_3.txt
# (you can use column selection in your editor)
sh rename.sh
Конечно, это очевидно, но обычно это намного быстрее, чем возиться со сложными (и одним -из всех )скриптов.
(отредактировано позже )Комментарии верны, что в этом случае использование этой техники может привести к потере данных(data1_3.txt
может быть уничтожено ). Поэтому безопасно сделать перемещение в два шага :сначала изменить имена, но переместить файл в какое-то безопасное место (либо добавить произвольное ложное расширение, либо создать временный подкаталог, куда перемещаются все файлы ), и затем на втором этапе переместите все файлы в их конечное место назначения.
Вот фрагмент bash для выполнения этой работы, предполагая, что файлы действительно названы так, как вы их изобразили(data<one-digit>_<digits>.txt
).
shopt -s extglob
#gather files into array
files=( data[[:digit:]]_+([[:digit:]]).txt )
#zip original files with their target file names and feed to mv
paste <(printf '%s\n' "${files[@]}" | sort -k1.5,1n -k2n -t'_') \
<(printf '%s.ren\n' "${files[@]}" | sort -k1.5,1n -k2nr -t'_') |
xargs -n 2 mv --
#strip the temporary.ren suffix
for f in data*.ren; do mv -- "$f" "${f%.ren}"; done
Сначала переименуйте все файлы с префиксом «старый -»:
for i in *
do
mv "$i" "old-$i"
done
Затем запустите эту команду и просмотрите вывод, чтобы убедиться, что он выглядит хорошо:
ls -v | tac | sort -s -t _ -k1,1 | sed -e 's/^old-//' | paste <(ls -v) - | sed -e 's/^/mv /'
Если это так, направьте вывод в sh.
Вот что происходит.
ls -v
производит их в отсортированном порядке (-v говорит о сортировке 11 после 9, например)_
. Оба параметра-k1,1
и-s
важны для получения правильного результата. Без -k1,1 остальная часть строки используется для разрешения дубликатов, которые нам не нужны, а без -s
дубликаты упорядочиваются произвольно. Остальное достаточно просто.