替换所有出现的字符,除非被两个不同的模式包围

Replace all occurances of a character unless surrounded by two different patterns

我想找到一个正则表达式(最好是在 perl 中,但任何风格都可以)来替换每个 _ 除了前面正好是 8 位数字 后跟恰好 6 位数字。
实际上,我想替换文件名中的 _ 除了那些格式为 YYYYMMDD_hhmmss.
的日期 一般来说,我想替换某些字符的所有出现,这些字符 前面没有某种模式 后面没有其他模式.
我尝试了很多正则表达式并在网上查找了很多,但我没有找到任何东西!

我知道可以用 . 替换每个 _,然后在 YYYYMMDD.hhmmss 中恢复 _,但我有兴趣一步完成(希望这是可能的)。

以下是一些替换示例:

Patate_17890505_TitreEnCamelCase.ext  -->  Patate.17890505.TitreEnCamelCase.ext
EPFL_AlgebreLineaire                  -->  EPFL.AlgebreLineaire
ipe.20210302_005606.pdf               -->  ipe.20210302_005606.pdf
1_                                    -->  1.
12_                                   -->  12.
_1                                    -->  .1
_12                                   -->  .12
12345678_                             -->  12345678.
_123456                               -->  .123456
12345678_12345                        -->  12345678.12345
1234567_123456                        -->  1234567.123456
1234567_12345                         -->  1234567.12345
123456_12345                          -->  123456.12345
12345678_1234567                      -->  12345678.1234567
123456789_123456                      -->  123456789.123456
123456789_1234567                     -->  123456789.1234567
_patate__truc__                       -->  .patate..truc..
___                                   -->  ...
foo_12345678                          -->  foo.12345678
foo_12345678_123456_bar               -->  foo.12345678_123456.bar
12345678_123456                       -->  12345678_123456
foo12345678_123456bar                 -->  foo12345678_123456bar

下面是我试过的几个例子。


与我想要的完全相反,替换每个_前面恰好8位数字和后面恰好6位数字(在[=37上尝试=]):

s/((?<!\d)(?:\d{8}))_((?:\d{6})(?!\d))/./g

它有效,所以我需要这个正则表达式的否定…


只是消极的后视和消极的前瞻(在 regex101 上尝试):

s/(?<!\d{8})_(?!\d{6})/./g

失败:如果 _ 前面正好有 8 位数字 后面正好有 6 位数字,则不会替换,例如 _ 在这些字符串中未被替换:

12345678_
_123456
12345678_12345
1234567_123456

我需要替换除“and”之外的所有内容,但是这个替换了除“or”之外的所有内容(所以它遗漏了一些_)。


灵感来自 this answer (from python regex: match a char surrounded by exactly 2 chars) (try it on regex101):

s/(?<!(?<!\d)\d{8})_(?!\d{6}(?!\d))/./g

失败:同上一个原因。
原始答案中的正则表达式有效,因为它替换了字符 前面有一个预模式 后跟 post-模式.


灵感来自 this answer (from Replace character UNLESS surrounded by specific tag), but I do not really understand how it works (try it on regex101):

s/_(?:(?!(?:.*?\d{6}))|(?=[^\d]+\d{8}))/./g

失败:在这些示例中,_ 未被替换

_123456
1234567_123456
12345678_1234567
123456789_123456
123456789_1234567
foo_12345678

原来的问题和我的很接近,但是不是\d{8}\d{6},pre-pattern和post-pattern是HTML标签,所以问题更简单:<tag></tag> 是独特的元素,对于我的问题,post-模式 \d{6} 后面可以跟另一个数字(同样是预模式\d{8} 前面可以有其他数字)。
但是这个几乎可以工作,与之前的尝试不同,它替换了这两个字符串中的 _

12345678_
12345678_12345

所以也许修改可以使它按我想要的方式工作......

您可以使用

(?<!\d)\d{8}_\d{6}(?!\d)(*SKIP)(*F)|_

参见regex demo详情:

  • (?<!\d)\d{8}_\d{6}(?!\d) - 八位数字,_ 和不包含任何其他数字的六位数字
  • (*SKIP)(*F) - 在当前位置匹配失败并从失败位置继续正则表达式搜索
  • | - 或
  • _ - 任何其他上下文中的下划线。

另一种正则表达式是

_(?!(?<=(?<!\d)\d{8}_)\d{6}(?!\d))

参见 this regex demo详情:

  • _ - 下划线
  • (?!(?<=(?<!\d)\d{8}_)\d{6}(?!\d)) - 如果在当前位置的右侧 - 有六位(且不超过六位)紧接八位数字和下划线的数字,则匹配失败的否定前瞻。