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

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

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 ... 

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

任何建议将不胜感激!

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

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

首先,我建议通过使用-n标志运行它来告诉rename显示它会做什么:

 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经验的人会发布一个更漂亮的解决方案。

我强烈推荐Oli的教程在Ubuntu中批量重命名文件; 最简单的重命名命令介绍,用于编写rename命令的简单介绍。


特定的rename命令如何工作:

这是s/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e作用:

  • 领先s意思是搜索要替换的文本。
  • 正则表达式/\d+/匹配一个或多个( + )数字( \d )。 这与您的0001相匹配。
  • 命令sprintf("%04dc%d", int(($& - 1) / 2) + 1, 2 - $& % 2)已构建。 $&代表比赛。 /通常结束替换文本,但\/使文字/ (这是除法,如下所述)。
  • 尾随/e表示将替换文本评估为代码。
    (尝试在最后使用/而不是/e运行它, 但请确保保留-n标志!

因此,您的新文件名是sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)的返回值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.该算术将00010002发送到000100030002000500060003 ,等等。
  • 2 - $& % 2将从原始文件名中提取的数字除以2 - $& % 2的剩余部分( %表示),如果是偶数则为0,如果是奇数则为1。 然后它从2中减去该算法。该算法将0001发送到0002000300042 ,依此类推。

最后, ome????.tif是一个glob , 你的shell扩展到当前目录中以ome开头的所有文件名的列表,以.tif结尾,并且其间恰好有四个字符。

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

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

我在Bash中使用了一种方法,基于如下思想:如果文件名中的数字是偶数,我们想将它除以2,并添加c2 ,如果数字是奇数,我们想要添加一个,然后除以2,并加上c1 。 像这样分开处理奇数和偶数文件比Eliah Kagan的Bash方法长得多,我同意使用Eliah Kagan这个其他答案中的 rename是聪明的方法,但这种方法在某些情况下可能有用。

与使用类似{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 

mv语句之前的echo仅用于测试。 如果您正在查看要执行的操作,请将其删除以实际重命名文件。

笔记

 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将被bc <<< "$h/2"输出的数字替换为4位格式,填充前导零(所以0 = 0000,10 = 0010等)。

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

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

-v用于详细, -n用于no-clobber(不覆盖已经具有预期名称的文件,如果它们存在)和--防止文件名以-开头的错误- (但由于我脚本的其余部分需要你的文件被命名为ome[somenumber].tif我想我只是出于习惯而加入它。


填补空白

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

如果这种方法适合你,你可以这样做:

 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++))将变量递增1(这计算循环的迭代次数)
  • printf -v new将以下语句放入变量new
  • "ome%04dc%d.tif"新文件名,其数字格式将替换为随后提到的数字
  • $(((i+1)/2))循环运行的次数加1,除以2

    这是基于Bash只进行整数除法,所以当我们将奇数除以2时,我们得到的结果与我们将前面的偶数除以2得到的结果相同:

     $ echo $((2/2)) 1 $ echo $((3/2)) 1 
  • $(((i+1)%2+1))除以循环运行的次数加上一乘二加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 

如果你真的想,你可以为此编写一个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 ,而不是只有一个。

请记住, rename -n中的-nmv -n rename -n并不相同。 运行rename -n根本不会移动文件。 运行mv -n移动文件,除非它必须覆盖目的地的现有文件才能这样做,也就是说mv -n为你提供了rename自动获得的安全性(除非你运行rename -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十二次之后运行命令,每次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.tif扩展为ome0001.tifome0002.tifome0003.tif等之一,具体取决于i拥有的值。 通过编写{0001..0012}而不是{0001..0012}来包括前导0使得这个参数变为mv ,它给出了文件的旧名称,编写起来很简单。
  • $( )是命令替换 。 在其中我运行一个printf命令,将第二个参数的所需文本输出到mv ,它给出了文件的新名称。 整个事情都附在" "引号中,因此避免了不必要的扩展 – 特别是泛化和分词。 在命令替换中, $(...)被替换为运行命令生成的输出 ...

因此,输出目标文件名的命令是:

 printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))" 
  • %04d%d具有与rename使用的 Perl sprintf函数相同的含义。
  • 两个参数中的每一个都使用算术扩展来执行计算。 整个$((...))被替换为评估表达式的结果...
  • 10#$ii$i )的值并将其视为基数为10的数字( 10# )。 这是必要的,因为Bash将前导0 s的数字视为八进制 。 2$(( ))里面你通常可以只写一个变量的名称来计算它(即i而不是$i ),但是$i也支持10#$i10#$i是需要它的少数情况之一在$(( ))里面。
  • 这里的算法与我在rename的算法相同,只是Bash中的除法是自动整数除法 – 它会自动截断小数部分 – 因此不必使用与Perl的int函数相对应的任何内容。

1 此站点上用于Bash代码的语法突出显示中的错误当前导致#之后的所有内容都显示为灰色。 一个不带引号的#通常会在Bash中发表评论 ,但在这种情况下它不会 。 你不必担心这个 – 你的Bash解释器不会犯同样的错误。

2 Perl实际上也将前导0 s的数字视为八进制。 但是,通过rename ,匹配变量$&实际上是一个字符串 – 毕竟这是文本处理。 Perl允许使用字符串,就好像它们是数字一样,当它出现时, 字符串中的前导0不会导致它被视为八进制数! 比较rename方式与这个更长,更困难,更不健壮的shell循环方法带来了一个共同的观察: Perl很奇怪,但它完成了工作。