关于在 calibre 中使用正则表达式的所有内容

正则表达式是 calibre 中许多地方使用的功能,用于对电子书内容和元数据执行复杂的操作。 本教程简要介绍了如何开始在 calibre 中使用正则表达式。

首先,一句警告和一句勇气

这不可避免地会有些技术性——毕竟,正则表达式是一种用于完成技术工作的技术工具。 我将不得不使用一些看起来复杂或令人费解的术语和概念。 我将尝试尽可能清楚地解释这些概念,但实际上根本无法不使用它们。 话虽如此,不要因为任何行话而气馁,因为我已经尝试解释所有新内容。虽然正则表达式本身可能看起来像是一种神秘的黑魔法(或者,更平淡地说,是一串随机的繁琐字母和符号),但我保证它们并不那么复杂。 即使那些非常了解正则表达式的人在阅读更复杂的表达式时也会遇到困难,但是编写它们并不那么困难 - 您可以逐步构建表达式。 那么,迈出一步,跟我一起进入兔子洞吧。

calibre 中哪里可以使用正则表达式?

calibre 有几个地方使用了正则表达式。 转换选项中有搜索和替换,导入设置中的文件名元数据检测以及批量编辑图书元数据时的搜索和替换。 calibre 图书编辑器还可以在其搜索和替换功能中使用正则表达式。 最后,您可以在搜索 calibre 图书列表以及在 calibre 电子书阅读器内搜索时使用正则表达式。

正则表达式到底是什么?

正则表达式是一种描述字符串集的方法。 单个正则表达式可以“匹配”多个不同的字符串。 这就是正则表达式如此强大的原因——它们是描述大量潜在变体的简洁方式。

备注

我在这里使用的字符串是在编程语言中使用的意义上的:由一个或多个字符组成的字符串,字符包括实际字符、数字、标点符号和所谓的空白(换行符、制表符等)。 请注意,一般情况下,大写和小写字符不被视为相同,因此“a”与“A”是不同的字符,依此类推。 在 calibre 中,正则表达式在搜索栏中不区分大小写,但在转换选项中则不区分大小写。 有一种方法可以使每个正则表达式不区分大小写,但我们稍后会讨论。 它变得很复杂,因为正则表达式允许其匹配的字符串存在变化,因此一个表达式可以匹配多个字符串,这就是人们费心使用它们的原因。 稍后会详细介绍。

介意解释一下吗?

嗯,这就是我们在这里的原因。 首先,这是正则表达式中最重要的概念:字符串本身就是一个与自身匹配的正则表达式。 也就是说,如果我想使用正则表达式来匹配字符串“Hello, World!”,则使用的正则表达式将是“Hello, World!”。 是的,确实就是这么简单。 不过,您会注意到,这 匹配确切的字符串 "Hello, World!",而不是例如 “你好,世界!”或“你好,世界!”或任何其他类似的变体。

听起来还不错。 下一步是什么?

接下来是真正好东西的开始。 还记得我说过正则表达式可以匹配多个字符串吗? 这就是事情变得有点复杂的地方。 假设,作为一个更实际的练习,您想要转换的电子书有一个讨厌的页脚来计算页数,例如“第 5 页,共 423 页”。 显然页码将从 1 增加到 423,因此您必须匹配 423 个不同的字符串,对吧? 实际上是错误的:正则表达式允许您定义匹配的字符集:要定义一个集合,您可以将想要包含在该集合中的所有字符放入方括号中。因此,例如,集合“[abc]”将匹配字符“a”、“b”或“c”。 集合将始终仅匹配集合中的一个字符。 它们“理解”字符范围,也就是说,如果您想匹配所有小写字符,您可以使用集合“[a-z]”来表示小写和大写字符,您可以使用“[a-zA” -Z]``等等。明白了吗? 因此,显然,使用表达式“Page [0-9] of 423”,您可以匹配前 9 页,从而将所需的表达式减少到三个:第二个表达式“Page [0-9]” [0-9] of 423`` 将匹配所有两位数页码,我相信您可以猜到第三个表达式会是什么样子。 好,去吧。 写下来。

嘿,整洁!这开始有意义了!

我本来希望你会这么说。 但请做好准备,现在情况会变得更好! 我们刚刚看到,使用集合,我们可以一次匹配多个字符之一。 但您甚至可以重复一个字符或集合,将处理上述页码示例所需的表达式数量减少到一个。 是的,一个! 兴奋的? 你应该! 它的工作原理是这样的:一些所谓的特殊字符,“+”,“?” 和“*”,重复它们前面的单个元素。 (元素意味着单个字符、字符集、转义序列或组(稍后我们将了解最后两个)——简而言之,正则表达式中的任何单个实体)。 这些字符称为通配符或量词。更准确地说,“?” 匹配前面元素的 0 或 1,“*”匹配前面元素的 0 或更多,“+”匹配前面元素的 1 或更多。 几个例子:表达式 a? 将匹配 “” (这是空字符串,在这种情况下严格来说没有用)或 “a”,表达式 a* 将匹配 “”, “ a”、”aa” 或任意数量的连续 a,最后,表达式 a+ 将匹配 “a”、”aa” 或任意数量的连续 a(注意:它不会 匹配空字符串!)。对于集合来说也是如此:表达式“[0-9]+”将匹配*每个整数*! 我知道您在想什么,您是对的:如果您在上述匹配页码的情况下使用它,那不是可以匹配所有页码的单个表达式吗? 是的,表达式“第 [0-9]+ 页,共 423 页”将匹配该书中的每个页码!

备注

关于这些量词的注释:它们通常会尝试匹配尽可能多的文本,因此使用它们时要小心。 这就是所谓的“贪婪行为”——我相信你明白为什么。 例如,当您尝试匹配标签时,就会出现问题。例如,考虑字符串 "<p class="calibre2"> Title here </p>" ,假设您想要匹配开始标签(第一对尖括号之间的部分,稍后会详细介绍标签)。 您可能认为表达式 <p.*> 会匹配该标签,但实际上,它匹配整个字符串! (字符“.”是另一个特殊字符。它匹配*除*换行符之外的任何内容,因此,基本上,表达式“.*”将匹配您能想到的任何单行)。相反,尝试使用 <p.*?> 这使得量词 "*" 非贪婪。 正如预期的那样,该表达式仅匹配第一个开始标记。 实际上还有另一种方法可以实现此目的:表达式 <p[^>]*> 将匹配相同的开始 标记 - 您将在下一节之后看到原因。 请注意,编写正则表达式的方法通常不止一种。

嗯,这些特殊字符非常整齐,但是如果我想匹配一个点或一个问号怎么办?

您当然可以这样做:只需在任何特殊字符前面放置一个反斜杠,它就会被解释为文字字符,没有任何特殊含义。 这对反斜杠后跟一个字符称为转义序列,将反斜杠放在特殊字符前面的行为称为转义该字符。 转义序列被解释为单个元素。 当然,有些转义序列的作用不仅仅是转义特殊字符,例如“”t“表示制表符。 稍后我们将讨论一些转义序列。 哦,顺便说一下,关于那些特殊字符:将我们在本介绍中讨论的任何字符视为具有某些特殊功能,因此如果您想要字面字符,则需要对其进行转义。

那么,最有用的集合是什么?

就知道你会问。 一些有用的集合是“[0-9]”匹配单个数字,“[a-z]”匹配单个小写字母,“[A-Z]”匹配单个大写字母,“[a-zA” -Z]`` 匹配单个字母,[a-zA-Z0-9] 匹配单个字母或数字。 您还可以使用转义序列作为简写:

\d

相当于``[0-9]``

\w

相当于``[a-zA-Z0-9_]``

\s

相当于任何空白

备注

“空白”是一个术语,指的是任何不会被打印的内容。 这些字符包括空格、制表符、换行符、换页符、回车符、不间断空格等。

备注

如果启用了搜索不区分大小写的设置,则大写和小写集可能会同时匹配大写和小写。 例如,可以在 calibre 本身的“首选项”->“搜索”以及 calibre“电子书阅读器”以及 calibre“编辑书籍”工具的搜索面板中找到此类设置。

作为集合的最后一个注释,您还可以将集合定义为集合中的任何字符*但*字符。 您可以通过将字符 "^" 作为*集合中的第一个字符*来实现这一点。 因此,[^a] 将匹配除“a”之外的任何字符。 这就是所谓的补充集合。 我们之前看到的那些转义序列简写也可以补充:“\D”``表示任何非数字字符,因此相当于``[^0-9]。 您猜对了,可以使用相应的大写字母而不是小写字母来补充其他简写。 因此,回到上一节中的示例 <p[^>]*> ,现在您可以看到它使用的字符集尝试匹配除右尖括号之外的任何字符。

但是如果我想要匹配一些不同的字符串,事情会变得复杂吗?

不要害怕,生活仍然美好而轻松。 考虑以下示例:您要转换的书籍的每个奇数页上都写有“标题”,而每个偶数页上都写有“作者”。 印刷版看起来很棒,对吧? 但在电子书中,这很烦人。 您可以将整个表达式分组在普通括号中,并且字符 "|" 将让您匹配*其右侧的表达式*或*其左侧的表达式。 将它们结合起来就完成了。 对你来说太快了吗? 好的,首先,我们将奇数页和偶数页的表达式分组,从而将“(标题)( 作者 )”作为我们需要的两个表达式。 现在我们通过使用竖线(“|”``称为竖线字符)使事情变得更简单:如果您使用表达式``(标题 | 作者),您将获得“Title”的匹配项 ”(在奇数页上),或者匹配“作者”(在偶数页上)。 嗯,这不是很容易吗?

当然,您也可以使用竖线而不使用分组括号。 还记得我说过量词会重复它们前面的元素吗? 嗯,竖线的工作方式有点不同:表达式“ Title | 作者”也将匹配字符串“Title”或字符串“作者”,就像上面使用分组的示例一样。 竖线在其前后的整个表达式之间进行选择。 因此,如果您想匹配字符串“Calibre”和“calibre”,并且只想在大写和小写“c”之间进行选择,则必须使用表达式“(c|C)alibre”, 其中分组确保仅选择“c”。 如果你要使用“c|Calibre”,你会得到字符串“c”或字符串“Calibre”的匹配,这不是我们想要的。 简而言之:如果有疑问,请与竖线一起使用分组。

你错过了。。。

…等一下,还有最后一件非常巧妙的事情,你可以与团体一起做。 如果您之前有一个匹配的组,则可以稍后在表达式中使用对该组的引用:组的编号从 1 开始,您可以通过转义要引用的组的编号来引用它们,因此是第五组 将被引用为``5``。 因此,如果您在字符串“Test Test”中搜索``([^]+) 1``,您将匹配整个字符串!

一开始,你说有办法让正则表达式不区分大小写?

是的,我知道了,谢谢你的关注和提醒。 您可以使用称为标志的东西来告诉 calibre 您希望如何处理某些事情。 您可以通过使用特殊构造“(?flags go here)”在表达式中包含标志,显然,您可以将“flags go here”替换为您想要的特定标志。 为了忽略大小写,标志是“i”,因此您可以在表达式中包含“(?i)”。 因此,(?i)test 将匹配“Test”、“tEst”、“TEst”以及您能想到的任何大小写变体。

另一个有用的标志可以让点匹配任何字符,*包括*换行符,标志“s”。 如果您想在表达式中使用多个标志,只需将它们放在同一个语句中即可:(?is) 将忽略大小写并使点全部匹配。 无论您首先声明哪个标志,(?si) 都等同于上面的。

我想我现在开始理解这些正则表达式了……我如何在 calibre 中使用它们?

转换

让我们从转换设置开始,这真的很简洁。 在“搜索和替换”部分中,您可以输入一个regexp(正则表达式的缩写)来描述转换过程中将被替换的字符串。 最巧妙的部分是向导。 单击向导人员,您可以预览转换过程中口径“看到”的内容。 向下滚动到要删除的字符串,选择并复制它,将其粘贴到窗口顶部的正则表达式字段中。 如果存在可变部分,例如页码等,请使用集合和量词来覆盖这些部分,并且在使用时,请记住转义特殊字符(如果有)。 点击标有“测试”的按钮,口径会突出显示如果您使用正则表达式它将替换的部分。 一旦您满意,请点击“确定”并进行转换。 如果您的转化源具有类似以下示例的标签,请务必小心:

Maybe, but the cops feel like you do, Anita. What's one more dead vampire?
New laws don't change that. </p>
<p class="calibre4"> <b class="calibre2">Generated by ABC Amber LIT Conv
<a href="http://www.processtext.com/abclit.html" class="calibre3">erter,
http://www.processtext.com/abclit.html</a></b></p>
<p class="calibre4"> It had only been two years since Addison v. Clark.
The court case gave us a revised version of what life was

(无耻地从“这个线程 <https://www.mobileread.com/forums/showthread.php?t=75594”>`_ )中撕下来。您还必须删除一些标签。在此示例中 ,我建议以标签``<b class=”calibre2”>``开始,现在你必须以相应的结束标签结束(开始标签是``<tag>``,结束标签是``</tag>``),在这种情况下这只是下一个``</b>`` 。(如果您对此不清楚,请参阅较好的 HTML 手册或在论坛中询问)。开始标记可以使用 <b.*?> 来描述,结束标记可以使用 </b>,因此我们可以使用 <b.*?>.*?</b> 删除这些标签之间的所有内容。但是使用这个表达式将是一个坏主意,因为它删除了 <b>- 标签包含的所有内容(顺便说一句,以粗体显示所包含的文本),并且我们很可能会以这种方式删除本书的部分内容。相反,也包括所包含字符串的开头,使正则表达式``<b.* ?>s*Generates+bys+ABCs+Ambers+LIT.*?</b>`` 带有量词的 \s 包含在此处,而不是像字符串中那样显式使用空格 捕获可能出现的字符串的任何变化。 请记住检查要删除的口径,以确保在测试新表达式时不会删除任何想要保留的部分。 如果您只检查一次出现的情况,则可能会错过文本中其他地方的不匹配之处。 另请注意,如果您不小心删除了比您实际想要的更多或更少的标签,calibre 会在删除后尝试修复损坏的代码。

添加书籍

您可以使用正则表达式的另一件事是从文件名中提取元数据。 您可以在设置的“添加图书”部分找到此功能。 这里有一个特殊功能:您可以使用元数据字段的字段名称,例如“(?P<title>)”将表明 calibre 使用这部分字符串作为书名。 窗口中列出了允许的字段名称,以及另一个不错的测试字段。举个例子:假设您要导入一大堆名为“古典文本:但丁·阿利吉耶里的神曲.mobi”的文件。 (显然,这已经在您的书库中,因为我们都喜欢古典意大利诗歌)或“科幻史诗:艾萨克·阿西莫夫的基础三部曲.epub”。 这显然是一个 calibre 不会从中提取任何有意义的数据的命名方案 - 它提取元数据的标准表达式是``(?P<title>.+) - (?P<author>[^_]+)``。此处适用的正则表达式为“[a-zA-Z]+: (?P<title>.+) by (?P<author>.+)”。 请注意,在元数据字段的组内,您需要使用表达式来描述字段实际匹配的内容。 另请注意,在使用 calibre 提供的测试字段时,您需要将文件扩展名添加到测试文件名中,否则即使使用工作表达式,您也根本不会获得任何匹配项。

批量编辑元数据

最后一部分是元数据字段中的正则表达式“搜索和替换”。 您可以通过选择书库中的多本书并使用批量元数据编辑来访问此内容。 使用最后一个功能时要非常小心,因为它可能会对您的书库造成**非常糟糕的事情**! 使用测试字段仔细检查您的表达式是否符合您的要求,并且仅标记您真正想要更改的书籍! 在正则表达式搜索模式下,您可以在一个字段中搜索,用某些内容替换文本,甚至可以将结果写入另一字段。一个实际的例子:假设您的书库包含弗兰克·赫伯特的《沙丘》系列书籍,以时尚“沙丘 1 - 沙丘”、“沙丘 2 - 沙丘弥赛亚”等命名。 现在你想让《沙丘》进入系列领域。 您可以通过在标题字段中搜索``(.*?) d+ - .*`` 并在系列字段中将其替换为``1`` 来做到这一点。看看我在那里做了什么? 这是对您要替换系列字段的第一组的引用。 现在您已经完成了该系列的准备,您只需再次搜索``.*? -`` 在标题字段中,并再次在标题字段中将其替换为 "" (空字符串),这样您的元数据就干净整洁了。是不是很棒? 顺便说一句,您也可以附加或添加到该字段,而不是替换整个字段,因此,如果您“希望”在书名前面添加系列信息,您也可以这样做。 您现在无疑已经注意到,有一个标记为“区分大小写”的复选框,因此您不必在此处使用标志来选择行为。

好了,关于正则表达式的简短介绍就到此结束了。 希望我能向您展示足够的内容,至少让您入门并让您能够继续自己学习 - 一个很好的起点是“正则表达式的 Python 文档”<https://docs.python.org/library/re.html>`_.

不过,最后一句警告是:正则表达式很强大,但也很容易出错。 calibre 提供了非常好的测试可能性,可以查看您的表达式是否按照您的预期运行。 使用它们。 尽量不要搬起石头砸自己的脚。 (上帝,我喜欢这个表达……)。 但是,如果您不顾警告,受伤了您的脚(或任何其他身体部位),请尝试从中吸取教训。

快速参考

荣誉

感谢您提供提示、更正等帮助:

  • ldolse

  • kovidgoyal

  • chaley

  • dwanthny

  • kacir

  • Starson17

  • Orpheu

有关正则表达式的更多信息,请参阅“Python 用户手册 <https://docs.python.org/library/re.html>”_。 calibre 使用的实际正则表达式书库是:regex,它支持对 Python 标准库的一些有用的增强。