如何向文件名添加交替字符串并成对重新编号?

如何向文件名添加交替字符串并成对重新编号?

使用高通量显微镜,我们生成了数千张图像。假设我们的系统将它们命名为:

ome0001.tif
ome0002.tif
ome0003.tif
ome0004.tif
ome0005.tif
ome0006.tif
ome0007.tif
ome0008.tif
ome0009.tif
ome0010.tif
ome0011.tif
ome0012.tif
...

我们希望交替插入c1c2相对于图像的数值,然后更改原始编号,以便每个连续的c1c2都包含相同的增量数字,遵循数字顺序(1,然后 2... 然后 9,然后 10)而不是字母数字顺序(1,然后 10,然后 2...)。

在我的例子中,结果如下:

ome0001c1.tif
ome0001c2.tif
ome0002c1.tif
ome0002c2.tif
ome0003c1.tif
ome0003c2.tif
ome0004c1.tif
ome0004c2.tif
ome0005c1.tif
ome0005c2.tif
ome0006c1.tif
ome0006c2.tif
...

我们无法通过终端命令行来做到这一点(生物学家说......)。

任何建议都将不胜感激!

答案1

rename执行批量重命名,它可以完成您需要的算术运算。

不同的 GNU/Linux 发行版有不同的命令,称为rename,具有不同的语法和功能。在 Debian、Ubuntu 和一些其他操作系统,rename是 Perl 重命名实用程序prename。它非常适合这项任务。

首先我建议rename告诉展示通过使用标志运行它,您可以知道它会做什么-n

rename -n 's/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e' ome????.tif

这应该向你展示:

rename(ome0001.tif, ome0001c1.tif)
rename(ome0002.tif, ome0001c2.tif)
rename(ome0003.tif, ome0002c1.tif)
rename(ome0004.tif, ome0002c2.tif)
rename(ome0005.tif, ome0003c1.tif)
rename(ome0006.tif, ome0003c2.tif)
rename(ome0007.tif, ome0004c1.tif)
rename(ome0008.tif, ome0004c2.tif)
rename(ome0009.tif, ome0005c1.tif)
rename(ome0010.tif, ome0005c2.tif)
rename(ome0011.tif, ome0006c1.tif)
rename(ome0012.tif, ome0006c2.tif)

假设这就是您想要的,请继续并在没有标志的情况下运行它-n(即,只需删除-n):

rename 's/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e' ome????.tif

该命令有点丑陋 - 尽管仍然比在 shell 中使用循环更优雅 - 也许比我拥有更多 Perl 经验的人会发布更漂亮的解决方案。

我强烈推荐奥利的教程在 Ubuntu 中批量重命名文件;rename 命令的简要介绍,简单介绍一下如何编写rename命令。


该特定rename命令的工作原理:

以下是具体操作s/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e

  • 前导s表示搜索要替换的文本。
  • 正则表达式/\d+/匹配一个或多个 ( +) 数字 ( \d)。这将匹配您的00010002等等。
  • 该命令sprintf("%04dc%d", int(($& - 1) / 2) + 1, 2 - $& % 2)已构建。$&表示匹配。/通常会结束替换文本,但\/会生成文字/(即除法,如下所述)。
  • 尾随/e意味着评价替换文本作为代码。 (尝试在末尾
    使用 just/而不是/e但一定要保留-n旗帜!

因此,您的新文件名是的返回值sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)。那么那里发生了什么?

  • sprintf返回格式化的文本。它的第一个参数是格式字符串在其中放置值。%04d使用第一个参数并将其格式化为 4 个字符宽的整数。%4d将省略前导零,因此%04d需要。不被任何覆盖%c意味着只是一个文字字母c。然后%d使用第二个参数并将其格式化为整数(使用默认格式)。
  • int(($& - 1) / 2) + 1从原始文件名中提取的数字中减去 1,将其除以 2,截断小数部分(int执行此操作),然后加 1。该算术将0001和发送0002000100030004发送到000200050006发送到0003,等等。
  • 2 - $& % 2取从原始文件名中提取的数字除以 2 的余数(%这样做),如果是偶数则为 0,如果是奇数则为 1。然后从 2 中减去该余数。此算法将发送000110002发送到20003发送到10004发送到2,依此类推。

最后,ome????.tif全局你的壳膨胀了列出当前目录中以 开头ome、以 结尾.tif、并且中间恰好有 4 个任意字符的所有文件名。

此列表被传递给rename命令,该命令将尝试重命名(或使用-n,告诉您如何重命名)所有名称包含与模式匹配的文件\d+

  • 根据您的描述,该目录中似乎没有任何以此方式命名的文件,但其中有些文件不是数字。
  • 但是如果您这样做了,那么您可以在上面显示的命令中出现的正则表达式中替换\d+\d{4}以确保它们没有被重命名,或者只是仔细检查生成的输出-n,无论如何您都应该这样做。
  • 我写了\d+而不是以\d{4}避免使命令变得比必要的更复杂。(有很多不同的方法来编写它。)

答案2

我在 Bash 中使用了一种方法来实现这一点,其思想是,如果文件名中的数字是偶数,我们要将其除以二,然后添加c2,如果数字是奇数,我们要将其加一,然后除以二,然后添加c1。像这样分别处理奇数和偶数文件比Eliah Kagan 的 Bash 方法并且我同意rename使用Eliah Kagan 的另一个回答是一种聪明的方法,但这种方法在某些情况下可能会有用。

与使用类似范围相比,这样做的一个优势{0000...0012}是它只尝试对现有文件进行操作,因此如果文件不存在,它不会抱怨。但是,如果有任何间隙,您仍然会得到不合逻辑的编号文件。请参阅我的答案的第二部分,了解不存在此问题的方法。

有一行代码看上去很糟糕:

for f in *; do g="${f%.tif}"; h="${g#ome}"; if [[ $(bc <<< "$h%2") == 0 ]]; then printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")" ; echo mv -vn -- "$f" "$new"; else printf -v new "ome%04dc1.tif" "$(bc <<< "($h+1)/2")"; echo mv -vn -- "$f" "$new"; fi; done

脚本如下:

#!/bin/bash

for f in *; do 
    g="${f%.tif}"
    h="${g#ome}"

    if [[ $(bc <<< "$h%2") == 0 ]]; then 
         printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")"
         echo mv -vn -- "$f" "$new"
    else
         printf -v new "ome%04dc1.tif" "$(bc <<< "($h+1)/2")"
         echo mv -vn -- "$f" "$new"
    fi
done

echo语句前面的 es仅mv用于测试。如果您看到了想要的结果,请删除它们以真正重命名文件。

笔记

g="${f%.tif}"     # strip off the extension
h="${g#ome}"      # strip off the letters... now h contains the number

测试数字是否为偶数(即除以 2 没有余数)

if [[ $(bc <<< "$h%2") == 0 ]]; then 

我已经使用了bc,它不会尝试将以零开头的数字视为八进制数,尽管我可以使用另一个字符串扩展去掉零,因为我无论如何都要将数字格式化为固定宽度。

接下来为偶数文件构造新名称:

printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")"

%04d将被 4 位数字格式的输出数字替换bc <<< "$h/2",并用前导零填充(因此 0 = 0000、10 = 0010,等等)。

使用构造的新名称重命名原始文件

echo mv -vn -- "$f" "$new"

-v对于详细、-n对于无破坏(如果存在,不要覆盖已经具有预期名称的文件)并--防止以 开头的文件名出现错误-(但由于我的脚本的其余部分希望您的文件被命名,ome[somenumber].tif我想我只是出于习惯添加它)。


填补空白

经过一些修改和 Eliah Kagan 的帮助,我找到了一种更简洁的方式来增加名称,这种方式的优点是可以填补空白。这种方法的问题在于,它只增加一个数字,对该数字进行一些简单的算术运算,格式化它,并将其放入文件名中。Bash 认为(可以这么说)“好的,这是下一个文件,我会给它下一个名字”,而不关注原始文件名。这意味着它创造了与旧名称无关的新名称,因此您将无法从逻辑上撤消重命名,并且只有当文件的名称已经可以按正确顺序处理时,文件才会按正确顺序重命名。您的示例中的情况就是如此,它具有固定宽度的零填充数字,但是如果您有名为 、 、 的文件,则2它们将按、、8、的顺序进行处理,这可能不是您想要的。1045102458

如果考虑到所有这些,这种方法适合您,您可以这样做:

i=0; for f in ome????.tif; do ((i++)); printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1)); echo mv -vn "$f" "$new"; done 

或者

#!/bin/bash
i=0

for f in ome????.tif; do 
    ((i++))
    printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1))
    echo mv -vn "$f" "$new"
done 

笔记

  • i=0初始化变量
  • ((i++))将变量加一(计算循环的迭代次数)
  • printf -v new将以下语句放入变量中new
  • "ome%04dc%d.tif"新的文件名,其数字格式将被随后提到的数字替换
  • $(((i+1)/2))循环运行的次数加一,除以 2

    这是基于 Bash 仅进行整数除法的基础,因此当我们将奇数除以 2 时,我们得到的答案与将前一个偶数除以 2 得到的答案相同:

    $ echo $((2/2))
    1
    $ echo $((3/2))
    1
    
  • $(((i+1)%2+1))将循环运行次数加一除以二,再加一,得到的余数。这意味着,如果迭代次数为奇数(例如第一次运行),则输出为1,如果迭代次数为偶数(例如第二次运行),则输出为2,得到c1c2
  • 我之所以使用,i=0是因为在运行过程中的任何时候,的值i将是循环运行的次数,这可能对调试有用,因为它也是正在处理的文件的序号(即,当 时i=69,我们正在处理第 69 个文件)。但是,我们可以从不同的 开始来简化算法i,例如:

    i=2; for f in ome????.tif; do printf -v new "ome%04dc%d.tif" $((i/2)) $((i%2+1)); echo mv -vn "$f" "$new"; ((i++)); done 
    

    有很多方法可以做到这一点:)

  • echo仅用于测试 - 如果看到想要的结果,请删除。

以下是此方法的作用示例:

$ ls
ome0002.tif  ome0004.tif  ome0007.tif  ome0009.tif  ome0010.tif  ome0012.tif  ome0019.tif  ome0100.tif  ome2996.tif
$ i=0; for f in ome????.tif; do ((i++)); printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1)); echo mv -vn "$f" "$new"; done 
mv -vn ome0002.tif ome0001c1.tif
mv -vn ome0004.tif ome0001c2.tif
mv -vn ome0007.tif ome0002c1.tif
mv -vn ome0009.tif ome0002c2.tif
mv -vn ome0010.tif ome0003c1.tif
mv -vn ome0012.tif ome0003c2.tif
mv -vn ome0019.tif ome0004c1.tif
mv -vn ome0100.tif ome0004c2.tif
mv -vn ome2996.tif ome0005c1.tif

答案3

如果您确实想要的话,您可以为此编写一个 shell 循环。

如果你想要一个可以在没有 的系统上运行的命令rename,或者rename命令不是 的系统上prename运行的命令,或者你希望它更容易被那些只知道 Bash 而不了解 Perl 的人理解,或者出于其他原因,你想在你的 shell 中将其实现为一个循环,调用mv命令,你可以。(否则,我建议rename使用我的其他答案对此。

Ubuntu 有 Bash 4,其中括号扩展保留前导零,因此{0001..0012}扩展为0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012这仅适用于您实际拥有某个范围内的所有文件的情况。根据您问题中的问题描述,情况似乎确实如此。否则,它仍然可以工作,但您会收到一大堆有关间隙的错误消息,这会让您很难注意到其他可能真正重要的错误。0012用您的实际上限替换。

自从echo出现在之前mv,此命令仅打印mv将要运行的命令,而不实际运行它们:1

for i in {0001..0012}; do echo mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"; done

这使用了与我的rename答案,无论是在算术方面,还是在格式字符串中%04d和的含义方面%d。这可以用 来完成{1..12},但这样会更加复杂,因为它需要用$( )进行两次命令替换printf,而不是一次。

请记住,-nin和in 的rename -n意思不一样。 运行根本不会移动文件。 运行会移动文件,除非它必须覆盖目标上的现有文件才能这样做,也就是说,它会自动为您提供安全性(除非您运行)。-nmv -nrename -nmv -nmv -nrenamerename -f要使上面显示的命令真正移动文件,请删除echo

for i in {0001..0012}; do mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"; done

Bash 循环的工作原理如下:

for i in {0001..0012}do运行12 次之后的命令,i每次都采用不同的值。此循环恰好在 之前只有一个这样的命令done,这表示循环体的结束。(从概念上讲,当控制碰到那个 时done,它会转到循环的下一次迭代,以i作为下一个值。)那个命令是:

mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"
  • $i在循环中出现几次。这是参数扩展,并将其替换为 的当前值i
  • ome$i.tifome0001.tif扩展为、ome0002.tif、等之一ome0003.tif,具体取决于i具有哪个值。通过将 改为 来包含前导 0 ,可将此参数改为,{0001..0012}从而给出文件的旧名称,写起来很简单。{1..12}mv
  • $( )命令替换。在其中我运行一个printf命令,该命令将第二个参数的所需文本输出到mv,从而给出文件的新名称。整个内容包含在" "引号因此不必要的扩张——具体来说,通配符单词拆分-- 避免使用。在命令替换中,$(...)替换为输出通过运行命令生成...

输出目标文件名的命令如下:

printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))"
  • %04d和Perl函数%d中的含义相同sprintf使用自rename
  • 两个参数均使用算术扩展进行计算。整体$((...))被替换为表达式的求值结果...
  • 10#$ii取( $i)的值,并且对待它作为一个十进制数字(10#)。这里需要这样做,因为 Bash 将以0s开头的数字视为八进制2在里面$(( ))你通常可以只写一个变量的名称来用它来计算(即,i而不是$i),但$i也受支持,并且10#$i是少数在里面需要它的情况之一$(( ))
  • 这里的算术和我一样使用自rename,不同之处在于 Bash 中的除法自动为整数除法——它会自动截断小数部分——因此不需要使用任何与 Perl 的int函数相对应的函数。

1 一个错误语法高亮目前,本网站上用于 Bash 代码的 会导致 之后的所有内容都#变成灰色。不带引号的#通常会以评论在 Bash 中,在这种情况下,它没有. 您不必担心这一点——您的 Bash 解释器不会犯同样的错误。

2 Perl 实际上也将以 s 开头的数字0视为八进制。但是,对于rename,匹配变量$&实际上是一个字符串——这是文本毕竟,Perl 允许将字符串当作数字来使用,并且当这样做时,0字符串中的前导 s细绳不要让它被视为八进制数!将这种rename方法与这种更长、更困难、更不可靠的 shell 循环方法进行比较,会让人想到一个常见的观察结果:Perl 很奇怪,但是它能完成工作。

相关内容