给定一个文件系统路径,有没有更短的方法提取文件名(不包括扩展名)?

305

我使用WPF C#编程。我有一个如下的路径:

C:\Program Files\hello.txt

我想从中提取 hello

路径是从数据库检索到的一个 字符串。目前我正在使用以下代码通过 '\''.' 进行拆分:

string path = "C:\\Program Files\\hello.txt";
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileName = fileArr.Last().ToString();

它可以运行,但我相信应该有更短、更聪明的解决方法。有什么想法吗?


在我的系统中,Path.GetFileName("C:\\dev\\some\\path\\to\\file.cs") 返回相同的字符串,而不是将其转换为 "file.cs"。如果我将代码复制/粘贴到在线编译器(如http://rextester.com/)中,则可以正常工作...? - jbyrd
10个回答

574


36

尝试

System.IO.Path.GetFileNameWithoutExtension(path); 

演示

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


似乎 Path.GetFileNameWithoutExtension() 无法处理文件扩展名长度大于3个字符的情况。 - Nolmë Informatique

29

20

11
string Location = "C:\\Program Files\\hello.txt";

string FileName = Location.Substring(Location.LastIndexOf('\\') +
    1);

1
+1,因为这可能在文件名包含无效字符[<,>等在Path.GetInvalidChars()中]的情况下作为备份时有帮助。 - bhuvin
当在UNIX ftp服务器上处理路径时,这实际上非常有用。 - s952163

11

试试这个:

string fileName = Path.GetFileNameWithoutExtension(@"C:\Program Files\hello.txt");

这将返回 fileName 的值 "hello"。


7
首先,问题中的代码并不会产生所描述的输出。它提取了文件的 扩展名 ("txt") 而非文件的 基本名称 ("hello")。要实现这一点,最后一行应该调用 First(),而不是 Last(),代码如下...
static string GetFileBaseNameUsingSplit(string path)
{
    string[] pathArr = path.Split('\\');
    string[] fileArr = pathArr.Last().Split('.');
    string fileBaseName = fileArr.First().ToString();

    return fileBaseName;
}

在进行了这个更改之后,需要考虑的一件事情是改进代码时它所创建的垃圾量:

  • 一个string[],包含path中每个路径段的string
  • 一个string[],至少包含path中最后一个路径段中每个.string

因此,从示例路径"C:\Program Files\hello.txt"中提取基本文件名应该产生(临时)object "C:""Program Files""hello.txt""hello""txt",以及一个string[3]和一个string[2]。如果在大量路径上调用此方法,则可能会产生重要影响。为了改进这一点,我们可以自己搜索path来定位基本名称的起始和结束点,并使用它们来创建一个新的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;
}

这里使用最后一个 \ 字符的索引后一个字符作为基本名称的起始点,然后从那里寻找第一个 . 作为基本名称结束后一个字符的索引。这比原来的代码更短吗?不完全是。这是一种“更聪明”的解决方案吗?我认为是的。至少,除了...问题之外是这样的。
正如你从注释中看到的那样,先前的方法存在问题。即使假设所有路径以带有扩展名的文件名结尾,它也会在路径以 \ 结尾(即目录路径)或者最后一个部分未包含扩展名时抛出异常。为了解决这个问题,我们需要添加额外的检查以防止 endIndex-1 (即未找到 .)。...
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;
}

现在这个版本比原来的版本更加高效且(现在)正确,但长度并没有缩短。

关于实现此功能的.NET方法,许多其他答案建议使用Path.GetFileNameWithoutExtension(),这是一个明显而简单的解决方案,但不能产生与问题中代码相同的结果GetFileBaseNameUsingSplit()Path.GetFileNameWithoutExtension() (GetFileBaseNameUsingPath()如下所示)之间存在微妙但重要的差异:前者提取第一个 . 之前的所有内容,后者提取最后一个 . 之前的所有内容。 对于问题中的样例path,这没有任何区别,但是看一下下面这张表格,比较上述四种方法在调用各种路径时的结果...

描述 方法 路径 结果
单扩展名 GetFileBaseNameUsingSplit() "C:\Program Files\hello.txt" "hello"
单扩展名 GetFileBaseNameUsingPath() "C:\Program Files\hello.txt" "hello"
单扩展名 GetFileBaseNameUsingSubstringUnsafe() "C:\Program Files\hello.txt" "hello"
单扩展名 GetFileBaseNameUsingSubstring() "C:\Program Files\hello.txt" "hello"




双扩展名 GetFileBaseNameUsingSplit() "C:\Program Files\hello.txt.ext" "hello"
双扩展名 GetFileBaseNameUsingPath() "C:\Program Files\hello.txt.ext" "hello.txt"
双扩展名 GetFileBaseNameUsingSubstringUnsafe() "C:\Program Files\hello.txt.ext" "hello"
双扩展名 GetFileBaseNameUsingSubstring() "C:\Program Files\hello.txt.ext" "hello"




无扩展名 GetFileBaseNameUsingSplit() "C:\Program Files\hello" "hello"
无扩展名 GetFileBaseNameUsingPath() "C:\Program Files\hello" "hello"
无扩展名 GetFileBaseNameUsingSubstringUnsafe() "C:\Program Files\hello" 异常: 长度不能小于零。(参数 'length')
无扩展名 GetFileBaseNameUsingSubstring() "C:\Program Files\hello" "hello"




前导点号 GetFileBaseNameUsingSplit() "C:\Program Files\.hello.txt" ""
前导点号 GetFileBaseNameUsingPath() "C:\Program Files\.hello.txt" ".hello"
前导点号 GetFileBaseNameUsingSubstringUnsafe() "C:\Program Files\.hello.txt" ""

...你会发现,当传递一个文件名具有双重扩展名或前/后缀.的路径时,Path.GetFileNameWithoutExtension()返回不同的结果。你可以使用以下代码自行尝试...

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);
        }
    }
}

简而言之,问题中的代码在某些边缘情况下表现不如人意。如果您要编写自己的路径操作代码,请务必考虑以下内容:

  • ...如何定义“没有扩展名的文件名”(是第一个 . 之前的所有内容还是最后一个 . 之前的所有内容?)
  • ...具有多个扩展名的文件
  • ...没有扩展名的文件
  • ...以 . 开头的文件
  • ...以 . 结尾的文件(在Windows上可能永远不会遇到这种情况,但它们是可能存在的)
  • ...具有“扩展名”的目录或包含 . 的目录
  • ...以\结尾的路径
  • ...相对路径

并非所有文件路径都遵循X:\ Directory \ File.ext的常规公式!


1
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

我很惊讶FileVersionInfo可以用于没有版本信息的文件。请注意,GetVersionInfo()只能用于引用已经存在的文件的路径。虽然这两个类都可以用于获取文件名,但问题还要求去除扩展名。 - Lance U. Matthews

-1
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
    

如果您要动态获取文件名,则在App.config文件中配置您的路径 -

    <userSettings>
        <ConsoleApplication13.Properties.Settings>
          <setting name="Filelocation" serializeAs="String">
            <value>D:\\DeleteFileTest</value>
          </setting>
              </ConsoleApplication13.Properties.Settings>
      </userSettings>

1
该问题询问如何从文件路径中提取没有扩展名的文件名。而这个代码片段则是检索指定在配置文件中或未指定的目录的直接子文件。它们并不是非常相似的事情。 - Lance U. Matthews

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接