Given a filesystem path, is there a shorter way to extract the filename without its extension?
I program in WPF C#. I have e.g. the following path:
C:\Program Files\hello.txt
and I want to extract hello
from it.
The path is a st开发者_高级运维ring
retrieved from a database. Currently I'm using the following code to split the path by '\'
and then split again by '.'
:
string path = "C:\\Program Files\\hello.txt";
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileName = fileArr.Last().ToString();
It works, but I believe there should be shorter and smarter solution to that. Any idea?
Path.GetFileName
Returns the file name and extension of a file path that is represented by a read-only character span.
Path.GetFileNameWithoutExtension
Returns the file name without the extension of a file path that is represented by a read-only character span.
The Path
class is wonderful.
try
fileName = Path.GetFileName (path);
http://msdn.microsoft.com/de-de/library/system.io.path.getfilename.aspx
try
System.IO.Path.GetFileNameWithoutExtension(path);
demo
string fileName = @"C:\mydir\myfile.ext";
string path = @"C:\mydir\";
string result;
result = Path.GetFileNameWithoutExtension(fileName);
Console.WriteLine("GetFileNameWithoutExtension('{0}') returns '{1}'",
fileName, result);
result = Path.GetFileName(path);
Console.WriteLine("GetFileName('{0}') returns '{1}'",
path, result);
// This code produces output similar to the following:
//
// GetFileNameWithoutExtension('C:\mydir\myfile.ext') returns 'myfile'
// GetFileName('C:\mydir\') returns ''
https://msdn.microsoft.com/en-gb/library/system.io.path.getfilenamewithoutextension%28v=vs.80%29.aspx
You can use Path API as follow:
var filenNme = Path.GetFileNameWithoutExtension([File Path]);
More info: Path.GetFileNameWithoutExtension
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
Path.GetFileNameWithoutExtension
Try this:
string fileName = Path.GetFileNameWithoutExtension(@"C:\Program Files\hello.txt");
This will return "hello" for fileName.
string Location = "C:\\Program Files\\hello.txt";
string FileName = Location.Substring(Location.LastIndexOf('\\') +
1);
Firstly, the code in the question does not produce the described output. It extracts the file extension ("txt"
) and not the file base name ("hello"
). To do that the last line should call First()
, not Last()
, like this...
static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
Having made that change, one thing to think about as far as improving this code is the amount of garbage it creates:
- A
string[]
containing onestring
for each path segment inpath
- A
string[]
containing at least onestring
for each.
in the last path segment inpath
Therefore, extracting the base file name from the sample path "C:\Program Files\hello.txt"
should produce the (temporary) object
s "C:"
, "Program Files"
, "hello.txt"
, "hello"
, "txt"
, a string[3]
, and a string[2]
. This could be significant if the method is called on a large number of paths. To improve this, we can search path
ourselves to locate the start and end points of the base name and use those to create one new string
...
static string GetFileBaseNameUsingSubstringUnsafe(string path)
{
// Fails on paths with no file extension - DO NOT USE!!
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
string fileBaseName = path.Substring(startIndex, endIndex - startIndex);
return fileBaseName;
}
This is using the index of the character after the last \
as the start of the base name, and from there looking for the first .
to use as the index of the character after the end of the base name. Is this shorter than the original code? Not quite. Is it a "smarter" solution? I think so. At least, it would be if not for the fact that...
As you can see from the comment, the previous method is problematic. Though it works if you assume all paths end with a file name with an extension, it will throw an exception if the path ends with \
(i.e. a directory path) or otherwise contains no extension in the last segment. To fix this, we need to add an extra check to account for when endIndex
is -1
(i.e. .
is not found)...
static string GetFileBaseNameUsingSubstring(string path)
{
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
int length = (endIndex >= 0 ? endIndex : path.Length) - startIndex;
string fileBaseName = path.Substring(startIndex, length);
return fileBaseName;
}
Now this version is nowhere near shorter than the original, but it is more efficient and (now) correct, too.
As far as .NET methods that implement this functionality, many other answers suggest using Path.GetFileNameWithoutExtension()
, which is an obvious, easy solution but does not produce the same results as the code in the question. There is a subtle but important difference between GetFileBaseNameUsingSplit()
and Path.GetFileNameWithoutExtension()
(GetFileBaseNameUsingPath()
below): the former extracts everything before the first .
and the latter extracts everything before the last .
. This doesn't make a difference for the sample path
in the question, but take a look at this table comparing the results of the above four methods when called with various paths...
Description | Method | Path | Result |
---|---|---|---|
Single extension | GetFileBaseNameUsingSplit() |
"C:\Program Files\hello.txt" |
"hello" |
Single extension | GetFileBaseNameUsingPath() |
"C:\Program Files\hello.txt" |
"hello" |
Single extension | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\hello.txt" |
"hello" |
Single extension | GetFileBaseNameUsingSubstring() |
"C:\Program Files\hello.txt" |
"hello" |
Double extension | GetFileBaseNameUsingSplit() |
"C:\Program Files\hello.txt.ext" |
"hello" |
Double extension | GetFileBaseNameUsingPath() |
"C:\Program Files\hello.txt.ext" |
"hello.txt" |
Double extension | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\hello.txt.ext" |
"hello" |
Double extension | GetFileBaseNameUsingSubstring() |
"C:\Program Files\hello.txt.ext" |
"hello" |
No extension | GetFileBaseNameUsingSplit() |
"C:\Program Files\hello" |
"hello" |
No extension | GetFileBaseNameUsingPath() |
"C:\Program Files\hello" |
"hello" |
No extension | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\hello" |
EXCEPTION: Length cannot be less than zero. (Parameter 'length') |
No extension | GetFileBaseNameUsingSubstring() |
"C:\Program Files\hello" |
"hello" |
Leading period | GetFileBaseNameUsingSplit() |
"C:\Program Files\.hello.txt" |
"" |
Leading period | GetFileBaseNameUsingPath() |
"C:\Program Files\.hello.txt" |
".hello" |
Leading period | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\.hello.txt" |
"" |
Leading period | GetFileBaseNameUsingSubstring() |
"C:\Program Files\.hello.txt" |
"" |
Trailing period | GetFileBaseNameUsingSplit() |
"C:\Program Files\hello.txt." |
"hello" |
Trailing period | GetFileBaseNameUsingPath() |
"C:\Program Files\hello.txt." |
"hello.txt" |
Trailing period | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\hello.txt." |
"hello" |
Trailing period | GetFileBaseNameUsingSubstring() |
"C:\Program Files\hello.txt." |
"hello" |
Directory path | GetFileBaseNameUsingSplit() |
"C:\Program Files\" |
"" |
Directory path | GetFileBaseNameUsingPath() |
"C:\Program Files\" |
"" |
Directory path | GetFileBaseNameUsingSubstringUnsafe() |
"C:\Program Files\" |
EXCEPTION: Length cannot be less than zero. (Parameter 'length') |
Directory path | GetFileBaseNameUsingSubstring() |
"C:\Program Files\" |
"" |
Current file path | GetFileBaseNameUsingSplit() |
"hello.txt" |
"hello" |
Current file path | GetFileBaseNameUsingPath() |
"hello.txt" |
"hello" |
Current file path | GetFileBaseNameUsingSubstringUnsafe() |
"hello.txt" |
"hello" |
Current file path | GetFileBaseNameUsingSubstring() |
"hello.txt" |
"hello" |
Parent file path | GetFileBaseNameUsingSplit() |
"..\hello.txt" |
"hello" |
Parent file path | GetFileBaseNameUsingPath() |
"..\hello.txt" |
"hello" |
Parent file path | GetFileBaseNameUsingSubstringUnsafe() |
"..\hello.txt" |
"hello" |
Parent file path | GetFileBaseNameUsingSubstring() |
"..\hello.txt" |
"hello" |
Parent directory path | GetFileBaseNameUsingSplit() |
".." |
"" |
Parent directory path | GetFileBaseNameUsingPath() |
".." |
"." |
Parent directory path | GetFileBaseNameUsingSubstringUnsafe() |
".." |
"" |
Parent directory path | GetFileBaseNameUsingSubstring() |
".." |
"" |
...and you'll see that Path.GetFileNameWithoutExtension()
yields different results when passed a path where the file name has a double extension or a leading and/or trailing .
. You can try it for yourself with the following code...
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace SO6921105
{
internal class PathExtractionResult
{
public string Description { get; set; }
public string Method { get; set; }
public string Path { get; set; }
public string Result { get; set; }
}
public static class Program
{
private static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
private static string GetFileBaseNameUsingPath(string path)
{
return Path.GetFileNameWithoutExtension(path);
}
private static string GetFileBaseNameUsingSubstringUnsafe(string path)
{
// Fails on paths with no file extension - DO NOT USE!!
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
string fileBaseName = path.Substring(startIndex, endIndex - startIndex);
return fileBaseName;
}
private static string GetFileBaseNameUsingSubstring(string path)
{
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
int length = (endIndex >= 0 ? endIndex : path.Length) - startIndex;
string fileBaseName = path.Substring(startIndex, length);
return fileBaseName;
}
public static void Main()
{
MethodInfo[] testMethods = typeof(Program).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Where(method => method.Name.StartsWith("GetFileBaseName"))
.ToArray();
var inputs = new[] {
new { Description = "Single extension", Path = @"C:\Program Files\hello.txt" },
new { Description = "Double extension", Path = @"C:\Program Files\hello.txt.ext" },
new { Description = "No extension", Path = @"C:\Program Files\hello" },
new { Description = "Leading period", Path = @"C:\Program Files\.hello.txt" },
new { Description = "Trailing period", Path = @"C:\Program Files\hello.txt." },
new { Description = "Directory path", Path = @"C:\Program Files\" },
new { Description = "Current file path", Path = "hello.txt" },
new { Description = "Parent file path", Path = @"..\hello.txt" },
new { Description = "Parent directory path", Path = ".." }
};
PathExtractionResult[] results = inputs
.SelectMany(
input => testMethods.Select(
method => {
string result;
try
{
string returnValue = (string) method.Invoke(null, new object[] { input.Path });
result = $"\"{returnValue}\"";
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
ex = ex.InnerException;
result = $"EXCEPTION: {ex.Message}";
}
return new PathExtractionResult() {
Description = input.Description,
Method = $"{method.Name}()",
Path = $"\"{input.Path}\"",
Result = result
};
}
)
).ToArray();
const int ColumnPadding = 2;
ResultWriter writer = new ResultWriter(Console.Out) {
DescriptionColumnWidth = results.Max(output => output.Description.Length) + ColumnPadding,
MethodColumnWidth = results.Max(output => output.Method.Length) + ColumnPadding,
PathColumnWidth = results.Max(output => output.Path.Length) + ColumnPadding,
ResultColumnWidth = results.Max(output => output.Result.Length) + ColumnPadding,
ItemLeftPadding = " ",
ItemRightPadding = " "
};
PathExtractionResult header = new PathExtractionResult() {
Description = nameof(PathExtractionResult.Description),
Method = nameof(PathExtractionResult.Method),
Path = nameof(PathExtractionResult.Path),
Result = nameof(PathExtractionResult.Result)
};
writer.WriteResult(header);
writer.WriteDivider();
foreach (IGrouping<string, PathExtractionResult> resultGroup in results.GroupBy(result => result.Description))
{
foreach (PathExtractionResult result in resultGroup)
writer.WriteResult(result);
writer.WriteDivider();
}
}
}
internal class ResultWriter
{
private const char DividerChar = '-';
private const char SeparatorChar = '|';
private TextWriter Writer { get; }
public ResultWriter(TextWriter writer)
{
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
}
public int DescriptionColumnWidth { get; set; }
public int MethodColumnWidth { get; set; }
public int PathColumnWidth { get; set; }
public int ResultColumnWidth { get; set; }
public string ItemLeftPadding { get; set; }
public string ItemRightPadding { get; set; }
public void WriteResult(PathExtractionResult result)
{
WriteLine(
$"{ItemLeftPadding}{result.Description}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Method}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Path}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Result}{ItemRightPadding}"
);
}
public void WriteDivider()
{
WriteLine(
new string(DividerChar, DescriptionColumnWidth),
new string(DividerChar, MethodColumnWidth),
new string(DividerChar, PathColumnWidth),
new string(DividerChar, ResultColumnWidth)
);
}
private void WriteLine(string description, string method, string path, string result)
{
Writer.Write(SeparatorChar);
Writer.Write(description.PadRight(DescriptionColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(method.PadRight(MethodColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(path.PadRight(PathColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(result.PadRight(ResultColumnWidth));
Writer.WriteLine(SeparatorChar);
}
}
}
TL;DR The code in the question does not behave as many seem to expect in some corner cases. If you're going to write your own path manipulation code, be sure to take into consideration...
- ...how you define a "filename without extension" (is it everything before the first
.
or everything before the last.
?) - ...files with multiple extensions
- ...files with no extension
- ...files with a leading
.
- ...files with a trailing
.
(probably not something you'll ever encounter on Windows, but they are possible) - ...directories with an "extension" or that otherwise contain a
.
- ...paths that end with a
\
- ...relative paths
Not all file paths follow the usual formula of X:\Directory\File.ext
!
string filepath = "C:\\Program Files\\example.txt";
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(filepath);
FileInfo fi = new FileInfo(filepath);
Console.WriteLine(fi.Name);
//input to the "fi" is a full path to the file from "filepath"
//This code will return the fileName from the given path
//output
//example.txt
Namespace: using System.IO;
//use this to get file name dynamically
string filelocation = Properties.Settings.Default.Filelocation;
//use this to get file name statically
//string filelocation = @"D:\FileDirectory\";
string[] filesname = Directory.GetFiles(filelocation); //for multiple files
Your path configuration in App.config file if you are going to get file name dynamically -
<userSettings>
<ConsoleApplication13.Properties.Settings>
<setting name="Filelocation" serializeAs="String">
<value>D:\\DeleteFileTest</value>
</setting>
</ConsoleApplication13.Properties.Settings>
</userSettings>
精彩评论