正则表达式中的 `(\S.*\S)` 和 `^\s*(.*)\s*$` 有什么区别?

16

我正在做RegexOne正则表达式教程,其中有一个问题是编写一个正则表达式来删除不必要的空格。

教程中提供的解决方案是:

我们可以通过在行内未捕获前导和尾随空格来跳过所有起始和结束空格。例如,表达式^\s*(.*)\s*$只会匹配内容。

问题的设置确实指示了在开头使用字符^和在结尾使用美元符号$,因此这个表达式是他们想要的:

我们先前已经看到如何使用插入符号^和美元符号$分别匹配完整的文本行。当与空格\s一起使用时,你可以轻松地跳过所有前导和尾随空格。

话虽如此,使用\S代替,我能够想出一个看起来更简单的解决方案- (\S.*\S)

我找到了与教程中匹配的Stack Overflow解决方案 - Regex Email - Ignore leading and trailing spaces?,我也看到其他指南推荐相同的格式,但我很难找到为什么\S是不好的解释。

此外,在他们的工具中,这被证明是正确的……那么,是否存在不能像提供的解决方案一样有效的情况?还是推荐版本只是一个标准格式?

1
如果在去除两端空格后,剩余文本为空或只包含一个字符,则这两个表达式将产生不同的结果。你的版本需要至少两个\S匹配才能进行匹配。 - jasonharper
1个回答

17
这个教程中^\s*(.*)\s*$的解决方案是错误的。捕获组.*是贪婪的,因此它会尽可能地扩展到行末 - 它将捕获尾随的空格。 .*永远不会回溯,因此后面的\s*将永远不会消耗任何字符。

https://regex101.com/r/584uVG/1

你的解决方案更好,因为它只匹配行中的非空格内容,但有几种奇怪的情况它无法匹配中间的非空格字符。(\S.*\S) 只会捕获至少两个字符,而教程中的技术(.*) 如果输入全是空白字符,则可能不捕获任何字符。(.*) 还可能只捕获一个字符。
但是,根据您链接中的问题描述:

偶尔,您会发现日志文件的空格格式不正确,其中行缩进太多或太少。一种解决方法是使用编辑器的搜索和替换以及正则表达式来提取行的内容,而不包括额外的空格。

从这个描述中,仅匹配非空格内容(就像你正在做的那样)可能不会删除不需要的前导和尾随空格。该教程可能想引导您使用一种可以用于匹配具有特定模式的整行的技术,然后仅将该行替换为仅捕获的组,例如:
匹配^\s*(.*\S)\s*$,替换为$1: https://regex101.com/r/584uVG/2/ 如果您有一种方法可以创建仅包含捕获组(或所有完全匹配项)的新文本文件,则您的技术将适用于问题。

const input = `   foo   
bar
  baz   
qux  `;
const newText = (input.match(/\S(?:$|.*\S)/gm) || [])
  .join('\n');
console.log(newText);


使用 \S 而不是 . 并不是坏事 - 如果知道一个特定位置必须由非空格字符匹配而不是空格,则使用 \S 更加精确,可以使模式的意图更清晰,并且可以更快地使错误匹配失败,还可以避免在某些情况下出现灾难性回溯问题。这些模式没有回溯问题,但养成良好习惯仍然是一个好习惯。


4
感谢您提供的详细回答!我尤其感激您解释了要匹配整个行而不仅仅是其中一部分的原因,这使得为什么缩写版本(例如 ^\s*(.*\S))不能使用更加清晰。不过,它可以避免仅捕获两个或更多字符的问题,我认为是这样的。我也感谢您(可能是无意中)介绍了 regex101 工具。我曾听说过它,但还没有看到过它的使用示例,所以这也让我受益匪浅。 - Catija

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接