2

I have an unusual project in which I need to retrieve the text after the second-last occurrence of the character "\", effectively giving me the last two directories in the following example strings:

  • D:\Archive Directory\2015-12-31 PM\SerialNo_01
  • D:\Archive Directory\2016-01-01\SerialNo_02
  • D:\Archive Directory\January 2016\SerialNo_03

The desired result is, respectively:

  • 2015-12-31 PM\SerialNo_01
  • 2016-01-01\SerialNo_02
  • January 2016\SerialNo_03

I'd like to do this as cleanly as possible and preferably in one line of code for each string.

This question is being answered by me after finding nothing on Stack Overflow about finding the second-last occurrence (or, for that matter, any Nth occurrence going backwards) of a string or character within a string in c#. If the community finds this question is duplicated or feels it is too obscure a case, I am willing to remove it.

Edit: Clarified that I don't need to do this as a list of strings; they will be run one at a time. I'm dynamically adding them as radio button controls to a form.

Dan A.
  • 195
  • 10
  • If interested in regex, [`\\[^\\]+\\(.*)$`](https://regex101.com/r/cB7cE4/1) with `gm` flags. – Tushar Jan 04 '16 at 06:07

5 Answers5

3

You don't need regex, you can rely on the built-in path handling provided by .NET.

var input = new List<string> { 
    @"D:\Archive Directory\2015-12-31 PM\SerialNo_01",
    @"D:\Archive Directory\2016-01-01\SerialNo_02",
    @"D:\Archive Directory\January 2016\SerialNo_03"
};

var result = input.Select(s => Path.Combine(Directory.GetParent(s).Name, Path.GetFileName(s)));

Yields:

2015-12-31 PM\SerialNo_01 
2016-01-01\SerialNo_02 
January 2016\SerialNo_03 

Then you don't need to worry about edge cases, or even cross-OS compatibility.

Rob
  • 26,989
  • 16
  • 82
  • 98
  • Does this still work if the "SerialNo_0x" portion is a directory, not a file? For some reason this wouldn't compile even when I changed `Path.GetFileName(s)` to `Path.GetDirectoryName(s)`; I got a "Cannot convert char to string" error on both references to `s`. – Dan A. Jan 04 '16 at 06:28
  • I am using each of the paths separately (not as a comma-separated list), but I don't think that would be the issue? – Dan A. Jan 04 '16 at 06:29
  • It doesn't know if it's a directory or file, but the above will work for both. The error is because you are probably using input as a string, not a list of strings – Rob Jan 04 '16 at 06:38
  • 1
    Ah - got it. I changed it to simply `var result = Path.Combine(Directory.GetParent(myString).Name, Path.GetFileName(myString));` and it works perfectly and is much more readable than my previous solution! Thanks! – Dan A. Jan 04 '16 at 06:44
1

I was able to come up with a solution after tweaking the code from this clever answer.

myString.Split('\\').Reverse().Take(2).Aggregate((s1, s2) => s2 + "\\" + s1);

This will split the string at each backslash, then reverse the resulting array of strings and take only the last two elements before concatenating them back together, now in reverse order, giving the desired result.

Community
  • 1
  • 1
Dan A.
  • 195
  • 10
  • 2
    This is an awfully wasteful way to get substring... Regular LastIndexOf is faster and does not produce insane number of strings/objects in process. – Alexei Levenkov Jan 04 '16 at 06:13
  • Agreed - that was the first thing I tried, but I'm not looking for the last index of backslash, I'm looking for the second-last index of it. Can you show me how you would use LastIndexOf to do this? – Dan A. Jan 04 '16 at 06:18
  • 1
    I would post question on SO asking "Is there LastIndexOf that can start searching backward from given position"... Maybe someone would search MSDN for me and give an answer. Than look for answers searching forward like http://stackoverflow.com/questions/2571716/find-nth-occurrence-of-a-character-in-a-string and copy-paste. – Alexei Levenkov Jan 04 '16 at 06:22
1

You can also match the parts you want.

(?<=\\)[^\\]*\\[^\\]*$

See demo.

https://regex101.com/r/fM9lY3/56

string strRegex = @"(?<=\\)[^\\]*\\[^\\]*$";
Regex myRegex = new Regex(strRegex, RegexOptions.Multiline);
string strTargetString = @"D:\Archive Directory\2015-12-31 PM\SerialNo_01" + "\n" + @"D:\Archive Directory\2016-01-01\SerialNo_02" + "\n" + @"D:\Archive Directory\January 2016\SerialNo_03";

foreach (Match myMatch in myRegex.Matches(strTargetString))
{
  if (myMatch.Success)
  {
    // Add your code here
  }
}
vks
  • 67,027
  • 10
  • 91
  • 124
0
If you still want to use Regex, you can use

Match match = Regex.Match(inputString,@".:\\.*\\(.*\\.*)");

if(match.success)
{
Result = match.Groups[1].value;
}

The first group in match will give the required result

For multiple results use Matches instead of match

Akash
  • 99
  • 1
  • 8
0
List<string> paths = new List<string> { 
                    @"D:\Archive Directory\2015-12-31 PM\SerialNo_01",
                    @"D:\Archive Directory\2016-01-01\SerialNo_02",
                    @"D:\Archive Directory\January 2016\SerialNo_03" };

var requiredPaths = paths.Select(item=> string.Join(@"\",item.Split('\\').
                                 Reverse().Take(2).Reverse()));
Sudhakar Tillapudi
  • 25,935
  • 5
  • 37
  • 67