更新文件名中的数字

问题描述

我有一组文件名,按数字顺序排序:

13B12363_1B1_0.png
13B12363_1B1_1.png
13B12363_1B1_2.png
13B12363_1B1_3.png
13B12363_1B1_4.png
13B12363_1B1_5.png
13B12363_1B1_6.png
13B12363_1B1_7.png
13B12363_1B1_8.png
13B12363_1B1_9.png
13B12363_1B1_10.png
[...]
13B12363_1B1_495.png
13B12363_1B1_496.png
13B12363_1B1_497.png
13B12363_1B1_498.png
13B12363_1B1_499.png

经过一些后处理,我删除了一些文件,我想更新订购号,并用新的位置替换实际的号。看着这个previous question,我最终会做类似的事情:

(1)ls -v | cat -n | while read n f; do mv -i $f ${f%%[0-9]+.png}_$n.png; done

但是,此命令不能识别“序号+ png”,而只是在文件名的末尾附加新编号。像13B12363_1B1_10.png_9.png

另一方面,如果我这样做:

(2)ls -v * | cat -n | while read n f; do mv $f ${f%.*}_$n.png; done

添加订购号没有问题。像13B12363_1B1_10_9.png

因此,对于(1),似乎我没有正确指定digit,但找不到正确的语法。到目前为止,我尝试了[0-9][0-9]+[[:digits:]][[:digits:]]+。哪个才是合适的?

此外,在(2)中,我想知道如何指定rename(CentOS版本)以删除第二个和第三个下划线之间的数字。这里我不得不说我有一些文件名,例如20B12363_22_10_9.png,所以我应该以某种方式指定第二第三下划线。

解决方法

使用Bash内置的Basic Regex Engine和null分隔的文件列表。

经过样品测试

#!/usr/bin/env bash

prename=$1

# Bash setting to return empty result if no match found
shopt -s nullglob

# Create a temporary directory to prevent file rename collisions
tmpdir=$(mktemp -d) || exit 1

# Add a trap to remove the temporary directory on EXIT
trap 'rmdir -- "$tmpdir"'  EXIT

# Initialize file counter
n=0

# Generate null delimited list of files
printf -- %s\\0 "${prename}_"*'.png' |

# Sort the null delimited list on 3rd field numeric order with _ separator
sort --zero-terminated --field-separator=_ --key=3n |

# Iterate the null delimited list
while IFS= read -r -d '' f; do
  
  # If Bash Regex match the file name AND
  # file has a different sequence number

  if [[ "$f" =~ (.*)_([0-9]+)\.png$ ]] && [[ ${BASH_REMATCH[2]} -ne $n ]]; then

    # Use captured Regex match group 1 to rename file with incrementing counter
    # and move it to the temporary folder to prevent rename collision with
    # existing file
    echo mv -- "$f" "$tmpdir/${BASH_REMATCH[1]}_$((n)).png"
  fi

  # Increment file counter
  n=$((n+1))
done

# Move back the renamed files in place
mv --no-clobber -- "$tmpdir/*" ./

# $tempdir removal is automatic on EXIT
# If something goes wrong,some files remain in it and it is not deleted
# so these can be dealt with manually

如果结果符合您的期望,请删除echo

样本输出

mv -- 13B12363_1B1_495.png /tmp/tmp.O2HmbyD7d5/13B12363_1B1_11.png
mv -- 13B12363_1B1_496.png /tmp/tmp.O2HmbyD7d5/13B12363_1B1_12.png
mv -- 13B12363_1B1_497.png /tmp/tmp.O2HmbyD7d5/13B12363_1B1_13.png
mv -- 13B12363_1B1_498.png /tmp/tmp.O2HmbyD7d5/13B12363_1B1_14.png
mv -- 13B12363_1B1_499.png /tmp/tmp.O2HmbyD7d5/13B12363_1B1_15.png
,

Do not parse ls

read解释\并在IFS上拆分。 bashfaq how to read a stream line by line

${f%%replacement}扩展中,替换的不是正则表达式,而是球形。规则不同。 +的字面意思是+

您可以先shopt -o extglob,然后再${f%%+([0-9]).png}。或编写一个循环。或者也匹配_并执行f=${f%%.png}; f="${f%_[0-9]*}_"

或者(未经测试):

find . -maxdepth 1 -mindepth 1 -type f -name '13B12363_1B1_*.png' |
sort -t_ -n -k3 |
sed 's/\(.*\)[0-9]+\.png$/&\t\1/' |
{
    n=1;
    while IFS=$'\t' read -r from to; do
       echo mv "$from" "$to$((n++)).png";
    done;
}
,

使用perl的另一种选择:

perl -e 'while(<@ARGV>){$o=$_;s/\d+(?=\D*$)/$i++.".renamed"/e;die if -e $_;rename $o,$_}while(<*.renamed>){$o=$_;s/\.renamed$//;die if -e $_;rename $o,$_}' $(ls -v|sed -E "s/$|^/'/g"|paste -sd ' ' -)

此解决方案应通过以下方式避免重命名冲突:首先重命名文件,添加额外的“ .renamed”扩展名。然后删除“ .renamed”扩展名作为最后一步。另外,还有检查来检测重命名冲突。

无论如何,请先备份您的数据,然后再尝试:)


展开并解释了perl脚本:

while(<@ARGV>){ # loop through arguments. 
                # filenames are passed to "$_" variable
    
    # save old file name
    $o=$_;

    # if not using variable,regex replacement (s///) uses topic variable ($_)
    # e flag ==> evals the replacement
    s/\d+(?=\D*$)/$i++.".renamed"/e;  # works on $_

    # Detect rename collision
    die if -e $_;

    rename $o,$_
}
while(<*.renamed>){
    $o=$_;
    s/\.renamed$//; # remove .renamed extension
    die if -e $_;
    rename $o,$_
}

正则表达式:

\d+       # one number or more
(?=\D*$)  # followed by 0 or more non-numbers and end of string