使用高通量显微镜,我们生成了数千张图像。假设我们的系统将它们命名为:
ome0001.tif
ome0002.tif
ome0003.tif
ome0004.tif
ome0005.tif
ome0006.tif
ome0007.tif
ome0008.tif
ome0009.tif
ome0010.tif
ome0011.tif
ome0012.tif
...
我们希望交替插入c1
和c2
相对于图像的数值,然后更改原始编号,以便每个连续的c1
和c2
都包含相同的增量数字,遵循数字顺序(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
)。这将匹配您的0001
、0002
等等。 - 该命令
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
和发送0002
到0001
,0003
和0004
发送到0002
,0005
和0006
发送到0003
,等等。2 - $& % 2
取从原始文件名中提取的数字除以 2 的余数(%
这样做),如果是偶数则为 0,如果是奇数则为 1。然后从 2 中减去该余数。此算法将发送0001
到1
、0002
发送到2
、0003
发送到1
、0004
发送到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
、的顺序进行处理,这可能不是您想要的。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++))
将变量加一(计算循环的迭代次数)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
,得到c1
或c2
我之所以使用,
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
,而不是一次。
请记住,-n
in和in 的rename -n
意思不一样。 运行根本不会移动文件。 运行会移动文件,除非它必须覆盖目标上的现有文件才能这样做,也就是说,它会自动为您提供安全性(除非您运行)。-n
mv -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
运行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.tif
ome0001.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#$i
i
取($i
)的值,并且对待它作为一个十进制数字(10#
)。这里需要这样做,因为 Bash 将以0
s开头的数字视为八进制。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 很奇怪,但是它能完成工作。