【问题标题】:Regex including what is supposed to be non-capturing group in result正则表达式,包括结果中应该是非捕获组的内容
【发布时间】:2017-08-12 16:59:03
【问题描述】:

我有以下简单的测试,我正在尝试获取正则表达式模式,以便它提取不带“.exe”后缀的可执行文件名称。
 
我的非捕获组设置 (?:\\.exe) 似乎不起作用,或者我误解了它的工作原理。
 
regex101regexstorm.net 都显示相同的结果,前者确认 "(?:\.exe)" 是非捕获匹配。
 
关于我做错了什么有什么想法吗?

// test variable for what i would otherwise acquire from Environment.CommandLine
var testEcl = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.exe\" /?"
var asmName = Regex.Match(testEcl, @"[^\\]+(?:\.exe)", RegexOptions.IgnoreCase).Value;
// expecting "MyApp" but I get "MyApp.exe"

我已经能够通过使用定义了组名的匹配模式来提取我想要的值,如下所示,但我想了解为什么非捕获组设置方法没有按我预期的方式工作到。

// test variable for what i would otherwise acquire from Environment.CommandLine
var testEcl = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.exe\" /?"
var asmName = Regex.Match(Environment.CommandLine, @"(?<fname>[^\\]+)(?<ext>\.exe)", 
    RegexOptions.IgnoreCase).Groups["fname"].Value;
// get the desired "MyApp" result

/eoq

【问题讨论】:

  • 要从var testEcl = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.exe\" /?" 获取MyApp,您可以使用Path.GetFileName 作为string directory = Path.GetFileName(testEcl);
  • @Ashik,感谢基于非正则表达式的建议。尝试过 Path.GetFileName() 和 .GetFileNameWithoutExtension() 都抛出 System.ArgumentException "Illegal characters in path。"结果。
  • 你的路径在开头和结尾都包含"。尝试去掉引号,就可以了

标签: c# .net regex


【解决方案1】:

(?:...) 是一个非捕获组,它匹配并仍然使用文本。这意味着该组匹配的文本部分仍然添加到整体匹配值中。

一般来说,如果你想匹配一些东西而不是消费,你需要使用lookarounds。因此,如果您需要匹配后面带有特定字符串的内容,请使用 positive lookahead, (?=...) 构造:

some_pattern(?=specific string) // if specific string comes immmediately after pattern
some_pattern(?=.*specific string) // if specific string comes anywhere after pattern

如果您需要匹配但之前“从匹配中排除”某些特定文本,请使用积极的后视

(?<=specific string)some_pattern // if specific string comes immmediately before pattern
(?<=specific string.*?)some_pattern // if specific string comes anywhere before pattern

请注意,.*?.* - 即带有 *+?{2,} 甚至 {1,3} 量词的模式 - 正则表达式引擎并不总是支持后向模式,但是,C# .NET 正则表达式引擎幸运地支持它们。 Python PyPi regex 模块、Vim、JGSoft 软件以及现在兼容 ECMAScript 2018 的 JavaScript 环境也支持它们。

在这种情况下,您可以捕获您需要获取的内容,并且只匹配上下文而不捕获:

var testEcl = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.exe\" /?";
var asmName = string.Empty; 
var m = Regex.Match(testEcl, @"([^\\]+)\.exe", RegexOptions.IgnoreCase);
if (m.Success)
{
    asmName = m.Groups[1].Value;
}
Console.WriteLine(asmName);

C# demo

详情

  • ([^\\]+) - 正在捕获组 1:除 \ 之外的一个或多个字符
  • \. - 文字点
  • exe - 文字 exe 子字符串。

由于我们只对捕获第 1 组内容感兴趣,因此我们抓取 m.Groups[1].Value,而不是整个 m.Value(包含 .exe)。

【讨论】:

    【解决方案2】:

    您使用的是non-capturing group。这里的重点是group这个词; 不捕获.exe,但一般的正则表达式仍然可以。

    您可能想要使用positive lookahead,它只是断言字符串必须满足匹配有效的条件,尽管该条件未被捕获。

    换句话说,你希望(?=,而不是(?:,在你的组的开始。

    前者仅当您枚举the Groups property 中的Match object 时;在你的情况下,你只是使用the Value property,所以普通组(\.exe)和非捕获组(?:\.exe)之间没有区别。

    要查看区别,请考虑以下测试程序:

    static void Main(string[] args)
    {
        var positiveInput = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.exe\" /?";
        Test(positiveInput, @"[^\\]+(\.exe)");
        Test(positiveInput, @"[^\\]+(?:\.exe)");
        Test(positiveInput, @"[^\\]+(?=\.exe)");
    
        var negativeInput = "\"D:\\src\\repos\\myprj\\bin\\Debug\\MyApp.dll\" /?";
        Test(negativeInput, @"[^\\]+(?=\.exe)");
    }
    
    static void Test(String input, String pattern)
    {
        Console.WriteLine($"Input: {input}");
        Console.WriteLine($"Regex pattern: {pattern}");
    
        var match = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
    
        if (match.Success)
        {
            Console.WriteLine("Matched: " + match.Value);
            for (int i = 0; i < match.Groups.Count; i++)
            {
                Console.WriteLine($"Groups[{i}]: {match.Groups[i]}");
            }
        }
        else
        {
            Console.WriteLine("No match.");
        }
        Console.WriteLine("---");
    }
    

    这个的输出是:

    Input: "D:\src\repos\myprj\bin\Debug\MyApp.exe" /?
    Regex pattern: [^\\]+(\.exe)
    Matched: MyApp.exe
    Groups[0]: MyApp.exe
    Groups[1]: .exe
    ---
    Input: "D:\src\repos\myprj\bin\Debug\MyApp.exe" /?
    Regex pattern: [^\\]+(?:\.exe)
    Matched: MyApp.exe
    Groups[0]: MyApp.exe
    ---
    Input: "D:\src\repos\myprj\bin\Debug\MyApp.exe" /?
    Regex pattern: [^\\]+(?=\.exe)
    Matched: MyApp
    Groups[0]: MyApp
    ---
    Input: "D:\src\repos\myprj\bin\Debug\MyApp.dll" /?
    Regex pattern: [^\\]+(?=\.exe)
    No match.
    ---
    

    第一个正则表达式 (@"[^\\]+(\.exe)") 将 \.exe 作为一个普通组。 当我们枚举 Groups 属性时,我们看到.exe 确实是我们输入中捕获的一个组。 (注意整个正则表达式本身就是一个组,因此Groups[0] 等于Value)。

    第二个正则表达式 (@"[^\\]+(?:\.exe)") 是您的问题中提供的。 与前一种情况相比,唯一的区别是 Groups 属性不包含 .exe 作为其条目之一。

    我建议您使用第三个正则表达式 (@"[^\\]+(?=\.exe)")。 现在,输入的.exe 部分根本不会被正则表达式捕获,但是正则表达式不会匹配字符串,除非它以.exe 结尾,如第四个场景所示。

    【讨论】:

      【解决方案3】:

      它会匹配非捕获组但不会捕获它,所以如果你想要未捕获的部分,你应该访问捕获组而不是整个匹配

      您可以在

      中访问组
      var asmName = Regex.Match(testEcl, @"([^\\]+)(?:\.exe)", RegexOptions.IgnoreCase);
      asmName.Groups[1].Value
      

      正则表达式的演示可以在here找到

      【讨论】:

      • 感谢您的回复。我正在寻找匹配的捕获部分,而不是 (?: ) 设置表示为非捕获组的部分。当我尝试上述检索 .Groups[1].Value 产生 "" 空字符串和 .Groups[0].Value 时,如果我只是拉 .Value 并且 Groups.Count 为 1,我得到的结果相同。
      • 感谢现在有效。根据我的其他响应,在这种情况下甚至不需要(? /跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多