我希望编写一个实用程序,使用正则表达式一次批量重命名一堆文件。我将一次重命名的文件遵循某个命名约定,我想使用已经在文件名中的数据将它们更改为新的命名约定;但目前并非所有文件都遵循相同的约定。
所以我希望能够编写一个通用的程序,让我在运行时输入文本框中的文件名模式,以及我想从文件名中提取的用于重命名的标记。
例如 - 假设我有一个名为[Coalgirls]_Suite_Precure_02_(1280x720_Blu-Ray_FLAC)_[33D74D55].mkv
的文件。我希望能够将此文件重命名为Suite Precure - Ep 02 [Coalgirls][33D74D55].mkv
这意味着我最好能够在重命名类似于[%group%]_Suite_Precure_%ep%_(...)_[%crc%].mkv
的内容之前进入我的程序,它会填充局部变量group
,ep
和crc
用于批量重命名。
我正在考虑的一个特定程序是mp3tag,用于将文件名转换为id3标签。它允许你输入%artist% - %album% - %tracknumber% - %title%之类的内容,它会将这4个令牌放入相应的id3标签中。
如何在不必让用户知道正则表达式语法的情况下创建与此类似的系统?
答案 0 :(得分:2)
正如usr所提到的,您可以使用%(?<name>[^%]+)%
提取搜索字符串中的所有命名占位符。这将使你“组”,“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);
}
}
}
}
答案 1 :(得分:1)
制作正则表达式模式%(?<name>[^%]+)%
。这将捕获字符串中被百分号包围的所有标记。
然后,使用Regex.Replace
替换它们:
var replaced = Regex.Replace(input, pattern, (Match m) => EvaluateToken(m.Groups["name"].Value));
Regex.Replace
可以进行回调,允许您提供动态值。