查找正则表达式以获取具有文件扩展名的子字符串

Find a regular expression to get substrings with file extensions

字符串有多种变体:

  1. "txt files `(*.txt)|*.txt|All files (*.*)|*.*`"
  2. "Image Files`|*.jpg;*.jpeg;*.png;`"
  3. "Excel Files `(*.xls, *.xlsx)|*.xls;*.xlsx|CSV Files (*.csv)|*.csv`"

子字符串可以以任何字符结尾(space, ',', '.', '|', ';') -没关系。

尝试了以下选项:“[^*].{3,4}(.?);", "[^*]+.(.?);”。

我需要一个正则表达式来得到string[] = {.jpg, .jpeg, ...},最好没有重复的元素。

真的需要正则表达式吗?

首先,如果您按 | 拆分,结果中的每个奇数条目都是一个扩展名列表。然后,您可以将 再次 拆分为 ; 以获得扩展,然后您可以将其展平为单个序列和 trim 起始 [=13] 的每个元素=].最后,获取不同的集合并将其放入数组中。

这一切都可以用 Split 和 Linq 来完成:

var extensions = filter.Split('|', StringSplitOptions.RemoveEmptyEntries)
                       .Where((x, i) => i % 2 != 0)
                       .SelectMany(x => x.Split(';', StringSplitOptions.RemoveEmptyEntries))
                       .Select(x => x.TrimStart('*'))
                       .Distinct()
                       .ToArray();

从拆分中删除空条目可确保如果您以分隔符结尾,它只会被忽略。

.NET Fiddle 上查看实际效果。

简单拆分

我想我也会用 Split 来做,应该可以这样做:

str.Split('*',';','|')
  .Where(s => s.StartsWith(".") && s[1..].All(Char.IsLetterOrDigit))
  .Distinct();

注意: 这不会对扩展的长度做出任何坚持。如果需要,您可以在 Where 中为这些情况添加一些内容,例如:

&& s.Length is >3 and <6

.. 3 或 4 长度的扩展是 4 到 5 之间的点长,这就是“大于 3 且小于 6”的来源。请注意,它使用模式匹配,这是最近添加的 c#。如果您的 c# 较旧,您将需要一些较旧的长度检查方式..


正则表达式

..但作为您学习 Regex 的机会,使用捕获组更容易从字符串中提取文件扩展名:

var r = new Regex(@"\*(?<x>\.\w{3,4})\b");
var arr = r.Matches()
  .Cast<Match>()
  .Select(m => m.Groups["x"].Value)
  .Distinct();

Regex 本身寻找文字 * 然后开始将字符捕获到名为 x 的组中 (?<x>。捕获的字符是:一个文字点,后跟 3 到 4 个单词字符 (a-z, 0-9)。我在 3 和 4 之间选择,因为您的代码选择了它,但注意扩展名可以更少或更多,因此您可以对其进行调整。 Regex 的最后一位要求在 3 或 4 个字符后有一个词边界 \b,因为我们不希望部分匹配超过 4 个字符的扩展名。单词边界意味着扩展在 3 或 4 个字符后完成(下一个字符是非单词字符)

要使用 LINQ 从中提取数据,我们必须执行类似 Cast 匹配的结果集合条目;它们已经是 Matches,但是 MatchCollection 没有实现 IEnumerable<T> 因为它很旧,所以它不兼容 LINQ,除非我们做一些像 Cast 这样的事情来做到这一点。

Select从捕获组中检索字符串值,这是.xxx扩展,Distinct删除重复项


你的正则表达式

至于为什么你的尝试没有奏效:

[^*]
    .{3,4}
          (.?)
              ;

这匹配

  • char 是除星号以外的任何字符,
  • 后跟 3 或 4 个任意字符,
  • 后跟零个或任何一个字符,被捕获到一个未命名的组中,
  • 后跟分号。

它可能会被调整以在某些情况下工作,但它似乎没有指定您正在寻找的字符模式

[^*]+
     .  
      (.?)
          ;

这匹配

  • 一个或多个字符是除星号以外的任何字符,
  • 后跟任何字符,
  • 后跟零个或捕获到未命名组的任何字符之一,
  • 后跟分号

我怀疑您认为 ^ 是一个允许匹配文字 * 的转义符 - 转义符是 \,

^ 用作 [ ] 中的第一个字符时,它的意思是“除此之外的所有字符”,所以我怀疑你试图匹配一个文字星号,但你实际上最终匹配了完全相反的字符

实际上,大多数字符在字符 class 内时会失去其特殊含义,因此 [*] 将是“匹配字面星号”,就像 \*

您的示例字符串似乎由括号之间的可选前导部分组成,后跟竖线 |,然后是扩展名。

如果要匹配数据的格式,可以使用同名的捕获组,Group.Captures Property.

\b[fF]iles\b[^*()]*(?:\([^()]*\))?\|\*(?<ext>\.[\w*]+)(?:[,;]\*(?<ext>\.[\w*]+))*

在部分中,模式匹配:

  • \b[fF]iles\b 匹配文件或文件
  • [^*()]* 可选择匹配除 * ( )
  • 之外的任何字符
  • (?:\([^()]*\))? 可选匹配括号之间的部分
  • \|\* 匹配 |*
  • (?<ext>\.[\w*]+) 命名组 ext,匹配 . 和 1+ 次单词 char 或 *
  • (?:非捕获组
    • [,;]\* 匹配 ,; 后跟 *
    • (?<ext>\.[\w*]+) 组相同模式 ext
  • )* 关闭非捕获组并可选择重复它以获取所有扩展

看到一个C# regex 101 demo and a C# demo.

例如

string pattern = @"\b[fF]iles\b[^*()]*(?:\([^()]*\))?\|\*(?<ext>\.[\w*]+)(?:[,;]\*(?<ext>\.[\w*]+))*";    
string input = @"txt files `(*.txt)|*.txt|All files (*.*)|*.*`
Image Files`|*.jpg;*.jpeg;*.png;`
Excel Files `(*.xls, *.xlsx)|*.xls;*.xlsx|CSV Files (*.csv)|*.csv`";

var strings = Regex.Matches(input, pattern)

.SelectMany(m => m.Groups["ext"].Captures.Select(c => c.Value))
.ToArray()
.Distinct();

foreach (var s in strings)
{
    Console.WriteLine(s);
}

输出

.txt
.*
.jpg
.jpeg
.png
.xls
.xlsx
.csv

另一个与数据格式无关的较短模式,匹配一个点和 3 个或 4 个单词字符或一个星号,并在左侧断言一个点:

 (?<=\*)\.(?:\w{3,4}\b|\*)

再看一个regex demo