250

Let's suppose I have the following regex:

-(\d+)-

and I want to replace, using C#, the Group 1 (\d+) with AA, to obtain:

-AA-

Now I'm replacing it using:

var text = "example-123-example";
var pattern = @"-(\d+)-";
var replaced = Regex.Replace(text, pattern, "-AA-"); 

But I don't really like this, because if I change the pattern to match _(\d+)_ instead, I will have to change the replacement string by _AA_ too, and this is against the DRY principle.

I'm looking for something like:

Keep the matched text exactly how it is, but change Group 1 by this text and Group 2 by another text...

Edit:
That was just an example. I'm just looking for a generic way of doing what I said above.

It should work for:

anything(\d+)more_text and any pattern you can imagine.

All I want to do is replace only groups, and keep the rest of the match.

0

9 Answers 9

413

A good idea could be to encapsulate everything inside groups, no matter if need to identify them or not. That way you can use them in your replacement string. For example:

var pattern = @"(-)(\d+)(-)";
var replaced = Regex.Replace(text, pattern, "$1AA$3"); 

or using a MatchEvaluator:

var replaced = Regex.Replace(text, pattern, m => m.Groups[1].Value + "AA" + m.Groups[3].Value);

Another way, slightly messy, could be using a lookbehind/lookahead:

(?<=-)(\d+)(?=-)

Sign up to request clarification or add additional context in comments.

9 Comments

I edited your answer to provide more info, but what you said is totally correct. Don't know how I missed that I could put everything inside groups, no matter if will use them or not :). In my opinion, that solution is much better and cleaner than using lookahead and lookbehinds.
small typo, your replacement pattern should be $1AA$3
In order for this to work, I had to add .Value to m.Groups[1] etc.
Also worth noting - if your replacement text starts with a number, the first solution ("$1AA$3") won't work as intended!
@OscarMederos you can also use non capturing groups - good for groups you don't use. In (?:foo)(bar), $1 will replace bar. more details
|
42

You can do this using lookahead and lookbehind:

var pattern = @"(?<=-)\d+(?=-)";
var replaced = Regex.Replace(text, pattern, "AA"); 

Comments

26

I also had need for this and I created the following extension method for it:

public static class RegexExtensions
{
    public static string ReplaceGroup(
        this Regex regex, string input, string groupName, string replacement)
    {
        return regex.Replace(
            input,
            m =>
            {
                var group = m.Groups[groupName];
                var sb = new StringBuilder();
                var previousCaptureEnd = 0;
                foreach (var capture in group.Captures.Cast<Capture>())
                {
                    var currentCaptureEnd =
                        capture.Index + capture.Length - m.Index;
                    var currentCaptureLength =
                        capture.Index - m.Index - previousCaptureEnd;
                    sb.Append(
                        m.Value.Substring(
                            previousCaptureEnd, currentCaptureLength));
                    sb.Append(replacement);
                    previousCaptureEnd = currentCaptureEnd;
                }
                sb.Append(m.Value.Substring(previousCaptureEnd));

                return sb.ToString();
            });
    }
}

Usage:

var input = @"[assembly: AssemblyFileVersion(""2.0.3.0"")][assembly: AssemblyFileVersion(""2.0.3.0"")]";
var regex = new Regex(@"AssemblyFileVersion\(""(?<version>(\d+\.?){4})""\)");


var result = regex.ReplaceGroup(input , "version", "1.2.3");

Result:

[assembly: AssemblyFileVersion("1.2.3")][assembly: AssemblyFileVersion("1.2.3")]

1 Comment

I like this implementation but it does not replace multiple matches. I posted a version which does
14

If you don't want to change your pattern you can use the Group Index and Length properties of a matched group.

var text = "example-123-example";
var pattern = @"-(\d+)-";
var regex = new RegEx(pattern);
var match = regex.Match(text);

var firstPart = text.Substring(0,match.Groups[1].Index);    
var secondPart = text.Substring(match.Groups[1].Index + match.Groups[1].Length);
var fullReplace = firstPart + "AA" + secondPart;

1 Comment

Please note that this assumes and will only work for the first occurence of the match.
6

Here is another nice clean option that does not require changing your pattern.

        var text = "example-123-example";
        var pattern = @"-(\d+)-";

        var replaced = Regex.Replace(text, pattern, (_match) =>
        {
            Group group = _match.Groups[1];
            string replace = "AA";
            return String.Format("{0}{1}{2}", _match.Value.Substring(0, group.Index - _match.Index), replace, _match.Value.Substring(group.Index - _match.Index + group.Length));
        });

Comments

3

In 2024 there's another, very performant option that:

  1. uses Span to prevent allocations, and avoids Substring and StringBuilder and other expensive stuff
  2. Works with multiple matches
  3. Incredibly simple and doesn't need you to update the pattern

Here's the code:

var outputString = MyRegex.Replace(inputString, m =>
{
    var grp = m.Groups[1];
    return string.Concat(
        m.ValueSpan.Slice(0, grp.Index - m.Index), //prior part
        PUT_REPLACEMENT_HERE, //replacement
        m.ValueSpan.Slice(grp.Index + grp.Length - m.Index) //trailing part
    );
});

P.S. ValueSpan is available in .NET 6 and later

P.P.S. If you're on .NET 7/8 - use [GeneratedRegex] to pre-build your regex at compile time

Comments

2

Replace code:

var text = "example-123-example";
var pattern = @"-(\d+)-";
var replaced = Regex.ReplaceGroupValue(text, pattern, 1, "AA");

Extension class:

public static class RegexExtensions
{
    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, string groupName, string destinationValue)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupName],
            p => destinationValue);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, int groupIdx, string destinationValue)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupIdx],
            p => destinationValue);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, string groupName, Func<string, string> destinationValueSelector)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupName],
            destinationValueSelector);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, int groupIdx, Func<string, string> destinationValueSelector)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupIdx],
            destinationValueSelector);
    }

    [Pure]
    private static string ReplaceGroupValue(
        Regex source,
        string input,
        Func<Match, Group> groupSelector,
        Func<string, string> destinationValueSelector)
    {
        var matchResult = source.Matches(input);

        if (matchResult.Count <= 0)
        {
            return input;
        }

        var text = input;

        foreach (var group in matchResult.OfType<Match>().Select(groupSelector).OrderByDescending(p => p.Index))
        {
            var begin = group.Index > 0 ? text.Substring(0, group.Index) : string.Empty;
            var end = group.Index + group.Length < text.Length
                ? text.Substring(group.Index + group.Length)
                : string.Empty;
            var destinationValue = destinationValueSelector.Invoke(group.Value);
            text = $"{begin}{destinationValue}{end}";
        }

        return text;
    }
}

2 Comments

Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others.
You can keep some of the people happy all the time and you can keep all of the people happy some of the time but you can never contribute an answer to Stack Overflow without someone being annoyed
1

Here is a version similar to Daniel's but replacing multiple matches:

public static string ReplaceGroup(string input, string pattern, RegexOptions options, string groupName, string replacement)
{
    Match match;
    while ((match = Regex.Match(input, pattern, options)).Success)
    {
        var group = match.Groups[groupName];

        var sb = new StringBuilder();

        // Anything before the match
        if (match.Index > 0)
            sb.Append(input.Substring(0, match.Index));

        // The match itself
        var startIndex = group.Index - match.Index;
        var length = group.Length;
        var original = match.Value;
        var prior = original.Substring(0, startIndex);
        var trailing = original.Substring(startIndex + length);
        sb.Append(prior);
        sb.Append(replacement);
        sb.Append(trailing);

        // Anything after the match
        if (match.Index + match.Length < input.Length)
            sb.Append(input.Substring(match.Index + match.Length));

        input = sb.ToString();
    }

    return input;

1 Comment

Very nice, just what I needed, thanks. It can cause an infinite loop if the replacement something that will match again though; just gotta be careful.
0

go through the below coding to get the separate group replacement.

new_bib = Regex.Replace(new_bib, @"(?s)(\\bibitem\[[^\]]+\]\{" + pat4 + @"\})[\s\n\v]*([\\\{\}a-zA-Z\.\s\,\;\\\#\\\$\\\%\\\&\*\@\\\!\\\^+\-\\\=\\\~\\\:\\\" + dblqt + @"\\\;\\\`\\\']{20,70})", delegate(Match mts)
                    {
                           var fg = mts.Groups[0].Value.ToString(); 
                           var fs = mts.Groups[1].Value.ToString();
                           var fss = mts.Groups[2].Value.ToString();
                               fss = Regex.Replace(fss, @"[\\\{\}\\\#\\\$\\\%\\\&\*\@\\\!\\\^+\-\\\=\\\~\\\:\\\" + dblqt + @"\\\;\\\`\\\']+", "");
                           return "<augroup>" + fss + "</augroup>" + fs;
                    }, RegexOptions.IgnoreCase);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.