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

Phi*_*e P 7 command-line batch-rename

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

ome0001.tif
ome0002.tif
ome0003.tif
ome0004.tif
ome0005.tif
ome0006.tif
ome0007.tif
ome0008.tif
ome0009.tif
ome0010.tif
ome0011.tif
ome0012.tif
...
Run Code Online (Sandbox Code Playgroud)

我们想交替插入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
...
Run Code Online (Sandbox Code Playgroud)

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

任何建议将不胜感激!

Eli*_*gan 11

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
Run Code Online (Sandbox Code Playgroud)

那应该告诉你:

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)
Run Code Online (Sandbox Code Playgroud)

假设这是你想要的,继续运行它而不带-n标志(即,只是 remove -n):

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)
Run Code Online (Sandbox Code Playgroud)

这个命令有点难看——尽管比在你的 shell 中使用循环更优雅——也许比我有更多 Perl 经验的人会发布一个更漂亮的解决方案。

我强烈推荐Oli的教程Bulk renaming files in Ubuntu;对重命名命令的最简短介绍,用于编写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。该算术发送000100020001000300040002000500060003,等等。
  • 2 - $& % 2取从原始文件名中提取的数字除以 2 的余数(%这样做),如果是偶数则为 0,如果为奇数则为 1。然后将其从 2 中减去。该算术发送00011000220003100042,依此类推。

最后,ome????.tif是一个glob您的 shell 将其扩展为当前目录中所有文件名的列表,这些文件名以 开头ome,以 结尾.tif,中间正好有四个任意字符。

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

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

  • 非常感谢以利亚!您的解决方案不仅(重命名 's/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e' ome?? ??.tif) 工作完美,但也非常快。我们在几秒钟内重命名了 18000 个文件。还要感谢 Zanna,更普遍地感谢这个社区。我们将研究 Oli 的教程在 Ubuntu 中批量重命名文件,希望从现在开始能够自己重命名。菲利普。 (2认同)

Zan*_*nna 6

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

与使用类似范围相比,这样做的一个小优势{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
Run Code Online (Sandbox Code Playgroud)

这是一个脚本:

#!/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
Run Code Online (Sandbox Code Playgroud)

echo前面加上ESmv声明只是用于测试。如果您看到要完成的操作,请删除它们以实际重命名文件。

笔记

g="${f%.tif}"     # strip off the extension
h="${g#ome}"      # strip off the letters... now h contains the number
Run Code Online (Sandbox Code Playgroud)

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

if [[ $(bc <<< "$h%2") == 0 ]]; then 
Run Code Online (Sandbox Code Playgroud)

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

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

printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")"
Run Code Online (Sandbox Code Playgroud)

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

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

echo mv -vn -- "$f" "$new"
Run Code Online (Sandbox Code Playgroud)

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


填补空白

在 Eliah Kagan 的一些修补和更多帮助之后,我找到了更简洁的方法来增加具有填补空白优势的名称。这种方式的问题是只增加一个数字,对这个数字做一些简单的算术运算,格式化它,然后把它放在文件名中。Bash 认为(可以这么说)“好吧,这是下一个文件,我会给它下一个名称”,而没有注意原始文件名。这意味着它会创建与旧名称无关的新名称,因此您将无法在逻辑上撤消重命名,并且仅当文件名称已经存在时才会以正确的顺序重命名文件以便进行处理以正确的顺序。在您的示例中就是这种情况,它具有固定宽度的零填充数字,但是如果您有命名的文件,例如,28, 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 
Run Code Online (Sandbox Code Playgroud)

或者

#!/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 
Run Code Online (Sandbox Code Playgroud)

笔记

  • 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
    
    Run Code Online (Sandbox Code Playgroud)
  • $(((i+1)%2+1))除循环运行次数后的余数加一乘二,加一。这意味着,如果迭代次数为奇数(例如第一次运行),则输出为1,如果迭代次数为偶数(例如第二次运行),则输出为2,给出c1c2
  • 我使用i=0因为 then 在运行期间的任何时候,的值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 
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 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
Run Code Online (Sandbox Code Playgroud)


Eli*_*gan 5

如果您真的愿意,您可以为此编写一个 shell 循环。

如果你想要一个命令在没有renamerename命令没有的系统上工作prename,或者你希望它更容易被懂 Bash 但不懂 Perl 的人理解,或者由于其他一些原因,你想将它实现为调用mv命令的shell 中的循环,您可以。(否则,我rename其他答案中推荐该方法。)

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

由于echo出现 before 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
Run Code Online (Sandbox Code Playgroud)

这使用与我的rename答案相同的基本思想,就算术而言,以及格式字符串的含义%04d%d格式字符串。这可以用 来完成{1..12},但它会更复杂,因为它需要$( )printf,而不是一个来替换两个命令。

请记住的一点是-nrename -n并不意味着同样的事情-nmv -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
Run Code Online (Sandbox Code Playgroud)

下面是 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))")"
Run Code Online (Sandbox Code Playgroud)
  • $i在循环中出现几次。这是参数扩展,它被替换为 的当前值i
  • ome$i.tif扩展到一个ome0001.tifome0002.tifome0003.tif等等,这取决于哪个值i了。通过写入{0001..0012}而不是包含前导 0{1..12}使此参数变为mv,这给出了文件的旧名称,易于编写。
  • $( )命令替换。在其中我运行一个printf命令,将第二个参数的所需文本输出到mv,它给出了文件的新名称。整个事情被封闭在" "引号如此不必要的扩展-特别是通配分词--are避免。在命令替换中,由运行命令产生的输出$(...)替换。...

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

printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))"
Run Code Online (Sandbox Code Playgroud)
  • %04d%d使用 from 的Perlsprintf函数具有相同的含义。rename
  • 这两个参数中的每一个都使用算术扩展来执行计算。整个$((...))被替换为计算表达式的结果...
  • 10#$ii( $i)的值并将其视为基数为 10 的数字 ( 10#)。这是必要的,因为 Bash 将带有前导0s 的数字视为八进制2在内部,$(( ))您通常可以只写一个变量的名称来计算它(即,i代替$i),但$i也受支持,并且10#$i是在 内部需要它的少数情况之一$(( ))
  • 这里的算术与我使用 fromrename相同,除了 Bash 中的除法是自动整数除法——它自动截断小数部分——所以没有必要使用任何与 Perlint函数相对应的东西。

1 此站点上用于 Bash 代码语法突出显示中的错误当前导致 之后的所有内容都#变灰。在 Bash 中,未加引号#注释通常会开始注释,但在本例中不是。您不必担心这一点——您的 Bash 解释器不会犯同样的错误。

2 Perl 实际上也将带有前导0s 的数字视为八进制。但是,使用rename,匹配变量$&实际上是一个字符串——毕竟这是文本处理。Perl 允许将字符串当作数字来使用,并且当它这样做时,字符串中的前导0s不会导致它被视为八进制数!将这种方式与这种更长、更困难、更不稳健的 shell 循环方法进行比较,我们会想到一个共同的观察结果:Perl 很奇怪,但它完成了工作。rename