我有很多文件的名称如下:
data1_1.txt
data1_2.txt
data1_3.txt
data2_1.txt
data2_2.txt
...
然而,这些都是以相反的顺序下载和命名的。我怎样才能批量重命名所有这些,以便结果是:
data1_3.txt
data1_2.txt
data1_1.txt
data2_2.txt
data2_1.txt
...
我的第一个想法只是 bash / zsh 脚本,但如果有其他工具可以更好地工作,请告诉我。
答案1
和zsh
:
autoload zmv # best in ~/.zshrc
typeset -A c=()
zmv -n '(*)_<->.txt(#qnOn)' '$1_$((++c[${(b)1}])).txt-renamed' &&
: zmv '(*)-renamed' '$1'
(如果愿意的话,删除-n
(dry-run) 和:
(并且记住在不进行空运行的情况下再次运行之前重新初始化c=()
))。
<->
: 类似于<1-12>
匹配某个范围内的十进制数字,但这里没有指定界限,因此匹配一个或多个十进制数字的任何序列。也可以写成[0-9]##
where##
iszsh
相当于 ERE+
。(#q...)
是个明确的指定语法全局限定符。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 开始的情况下才执行此操作,如示例中所示。它将把所有[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
。
答案2
这是一个 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
答案3
首先,将所有文件重命名为前缀“old-”:
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 之后)- tac 反转整个输入(整个文件;请耐心等待!)
- 排序表示仅对第一个之前的字符进行稳定排序
_
。这-k1,1
和-s
对于确保获得正确的输出都很重要。如果没有 -k1,1 ,该行的其余部分将用于解决重复项,这是我们不想要的,并且如果没有-s
重复项,则会任意排序。
剩下的就很容易了。
答案4
这是一个简单的解决方案,我相信它非常适合您的情况。我们在这里所做的是,首先检查我们通过前缀获得了多少个文件类型。这里的文件类型前缀是指 data1_, 数据2_等等。然后,对于每种前缀类型,我们获取可用文件的总数并将它们存储到名为 的数组中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