如何在运行时分隔字符串?

Posted

技术标签:

【中文标题】如何在运行时分隔字符串?【英文标题】:How can I delimitate a string at runtime? 【发布时间】:2012-12-15 19:29:29 【问题描述】:

我正在寻找一个实用程序来使用正则表达式一次批量重命名一堆文件。我将一次重命名的文件遵循特定的命名约定,我想使用文件名中已有的数据将它们更改为新的命名约定;但目前并非我的所有文件都遵循相同的约定。

所以我希望能够编写一个通用程序,让我在运行时将文件名的模式输入到文本框中,以及我想从文件名中提取哪些标记用于重命名。

例如 - 假设我有一个名为 [Coalgirls]_Suite_Precure_02_(1280x720_Blu-Ray_FLAC)_[33D74D55].mkv 的文件。我希望能够将此文件重命名为Suite Precure - Ep 02 [Coalgirls][33D74D55].mkv

这意味着我最好能够在重命名类似于 [%group%]_Suite_Precure_%ep%_(...)_[%crc%].mkv 的内容之前进入我的程序,它会填充局部变量 groupepcrc 以用于批量重命名。

我正在考虑的一个特定程序是 mp3tag,用于将文件名转换为 id3 标签。它可以让您输入 %artist% - %album% - %tracknumber% - %title% 之类的内容,然后将这 4 个标记放入相应的 id3 标签中。

如何在不让用户知道正则表达式的情况下制作一个类似的系统?

【问题讨论】:

总是添加输入和输出的例子。它使 1000% 更清晰易懂。 我看到你在我回答后澄清了你的问题。没有帮助吗? @usr 乍一看,您的回答似乎让我必须提前知道 CRC 的确切值才能捕获它。不过我可能读错了。 【参考方案1】:

正如 usr 所提到的,您可以使用%(?<name>[^%]+)% 提取搜索字符串中的所有命名占位符。这将为您提供“group”、“ep”和“crc”。

现在您需要扫描占位符之间的所有片段,并在正则表达式中的每个占位符处进行捕获。我将从上面遍历匹配项(您可以获取每个匹配项的开始偏移量和长度以浏览非占位符片段)。

(您的示例中有错误,我会假设最后一部分是正确的,我将删除神秘的(...))

它将构建一个如下所示的正则表达式:

^%(?<group>.*?)_Suite_Precure_(?<ep>.*?)_(?<crc>.*?).mkv$

将文字片段传递给 Regex.Escape,然后在正则表达式中使用它以正确处理麻烦的字符。

现在,对于每个文件名,您尝试将正则表达式与其匹配。如果匹配,您将获得该文件的占位符的值。然后获取这些占位符值并将它们合并到输出模式中,适当地替换占位符。这为您提供了新名称,您可以进行重命名。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace renamer

    class RenameImpl
    
        public static IEnumerable<Tuple<string,string>> RenameWithPatterns(
            string path, string curpattern, string newpattern,
            bool caseSensitive)
        
            var placeholderNames = new List<string>();

            // Extract all the cur_placeholders from the user's input pattern
            var input_regex = new Regex(@"(\%[^%]+\%)");
            var cur_matches = input_regex.Matches(curpattern);
            var new_matches = input_regex.Matches(newpattern);
            var regex_pattern = new StringBuilder();

            if (!caseSensitive)
                regex_pattern.Append("(?i)");
            regex_pattern.Append('^');

            // Do a pass over the matches and grab info about each capture
            var cur_placeholders = new List<Tuple<string, int, int>>();
            var new_placeholders = new List<Tuple<string, int, int>>();
            for (var i = 0; i < cur_matches.Count; ++i)
            
                var m = cur_matches[i];
                cur_placeholders.Add(new Tuple<string, int, int>(
                    m.Value, m.Index, m.Length));
            
            for (var i = 0; i < new_matches.Count; ++i)
            
                var m = new_matches[i];
                new_placeholders.Add(new Tuple<string, int, int>(
                    m.Value, m.Index, m.Length));
            

            // Build the regular expression
            for (var i = 0; i < cur_placeholders.Count; ++i)
            
                var ph = cur_placeholders[i];

                // Get the literal before the first capture if it is the first
                if (i == 0 && ph.Item2 > 0)
                    regex_pattern.Append(Regex.Escape(
                        curpattern.Substring(0, ph.Item2)));

                // Generate the capture for the placeholder
                regex_pattern.AppendFormat("(?<0>.*?)",
                    ph.Item1.Replace("%", ""));

                // The literal after the placeholder
                if (i + 1 == cur_placeholders.Count)
                    regex_pattern.Append(Regex.Escape(
                        curpattern.Substring(ph.Item2 + ph.Item3)));
                else
                    regex_pattern.Append(Regex.Escape(
                        curpattern.Substring(ph.Item2 + ph.Item3,
                        cur_placeholders[i + 1].Item2 - (ph.Item2 + ph.Item3))));
            

            regex_pattern.Append('$');

            var re = new Regex(regex_pattern.ToString());

            foreach (var pathname in Directory.EnumerateFileSystemEntries(path))
            
                var file = Path.GetFileName(pathname);
                var m = re.Match(file);

                if (!m.Success)
                    continue;

                // New name is initially same as target pattern 
                var newname = newpattern;

                // Iterate through the placeholder names
                for (var i = new_placeholders.Count; i > 0; --i)
                
                    // Target placeholder name
                    var tn = new_placeholders[i-1].Item1.Replace("%", "");

                    // Get captured value for this capture
                    var ct = m.Groups[tn].Value;

                    // Perform the replacement
                    newname = newname.Remove(new_placeholders[i - 1].Item2,
                        new_placeholders[i - 1].Item3);
                    newname = newname.Insert(new_placeholders[i - 1].Item2, ct);
                

                newname = Path.Combine(path, newname);
                yield return new Tuple<string, string>(pathname, newname);
            
        
    

【讨论】:

这无助于 usr 的回应,但我想我明白了混乱可能在哪里。我想使用通用令牌占位符,例如 %group%、%ep% 等...我不希望用户必须使用原始正则表达式语法。我正在考虑的一个特定程序是 mp3tag,用于将文件名转换为 id3 标签。它可以让您输入 %artist% - %album% - %tracknumber% - %title% 之类的内容,然后将这 4 个标记放入相应的 id3 标签中。如何在不让用户知道正则表达式语法的情况下制作类似于此的系统? 用户不必知道正则表达式的语法。您只是从占位符 [group] [crc] 等生成一个正则表达式,以及介于两者之间的文字文本。例如 [Album] - [Artist] - [Title].mp3 变为 [Title] ([Album], [Artist]).mp3。这就是用户会做的事情。这将做同样的事情:[A] - [B] - [C].mp3 变为 [C] ([A], [B]).mp3。只需生成一个正则表达式,用 (?.*?) 替换用户的占位符,然后正则表达式匹配将提取字符串的该部分并在组数组中将其命名为“name”。 在我的例子中,“-”和“-”和“.mp3”是文本片段,正则表达式匹配的东西提取了当前文件名在这些部分中的内容。 我已经更新了我的答案,包括一个我拼凑起来的实现来演示我建议的算法。我将其添加到答案中。 如果我有一个 curpattern "%songname%.mp3" 和 newpattern "the song is called %songname%.mp3" 那么它将按照您的指定转换文件名。占位符的名称无关紧要。匹配的输入被插入到输出模式具有该名称的输出中。【参考方案2】:

制作正则表达式模式%(?&lt;name&gt;[^%]+)%。这将捕获字符串中被百分号包围的所有标记。

然后,使用Regex.Replace 替换它们:

var replaced = Regex.Replace(input, pattern, (Match m) => EvaluateToken(m.Groups["name"].Value));

Regex.Replace 可以接受允许您提供动态值的回调。

【讨论】:

这听起来只适用于我已经知道具体存在的值...这是否允许我在不同的文件名中捕获各种不同的 CRC 值?请参阅我在原始帖子中编辑的示例... 现在我明白了。难道你不能让用户输入一个将感兴趣的值(如 CRC)捕获到命名组中的正则表达式吗? 问题是,我想输入大量文件以按照这种模式进行批量重命名。我可能有 24 个或更多符合原始模式的文件名,所以我希望它们一次全部正确重命名。

以上是关于如何在运行时分隔字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在字符串中分隔 SQL 变量

python计算时分秒 青少年编程电子学会python编程等级考试一级真题解析2020-12

如何在 SSMS 的块中分隔 SQL 代码?

如何把年月日时分秒的字符串转换成日期类型

如何更改字符串中分隔两种货币的逗号?

如何在胶合时分离(带有空格)字符串,使用数组中的键来检查它是否粘合?