【问题标题】:Is there any way to validate a filename?有什么方法可以验证文件名吗?
【发布时间】:2011-04-16 12:58:58
【问题描述】:

我认为这必须在 RTL 中的某个地方,但我看了看,但找不到。

function IsValidFilename(filename: string): boolean;
//returns True if it would be possible to create or open a file with
//this name, without modifying the current directory structure

换句话说,它必须指向有效本地或网络驱动器上的现有文件夹,并且不包含任何无效字符。我们有这样的事情吗? (如果它检查当前用户的访问权限以确保您可以访问相关文件夹,则会获得奖励积分。)

【问题讨论】:

  • 我有点迂腐,我知道,但是使用const FileName: string
  • 对了,你的问题实在是太模糊了。我认为DirectoryExists(ExtractFilePath(FileName)) 是必要的要求,ExtractFileName(FileName) 仅包含有效字符这一事实也是如此。但这并不意味着您可以在此目录中创建甚至读取文件。该目录很可能是只读的(例如 Program Files 或 Windows),因此您可以打开文件,但不能创建文件。您甚至可能无法阅读(例如,另一个用户的目录)。另外,如果文件存在但您可以重写它怎么办?我们应该返回 true 还是 false?
  • 更好:const FileName: TFileName.
  • @AndreasRejbrand-他要做一些 IO 操作(读/写文件)。在硬盘驱动器上,这将至少需要 12 毫秒(将磁头放在正确扇区上的平均时间)。实际上,它需要百分之几毫秒。我不认为 CONST 添加的增益 (ns) 在这种情况下会有所不同 :) PS:我不反对使用 CONST。我的意思是,在这种情况下,它不会有任何影响。
  • 好问题。可惜没有好的答案!

标签: delphi filenames


【解决方案1】:

这是我的版本:

function MValidFileName(AFileName: String): Boolean;
const InvalidChars: set of AnsiChar = [#0..#31, '\', '/', ':', '*', '?', '"', '<', '>', '|'];
      InvalidWords: array[0..21] of String = ('CON', 'PRN', 'AUX', 'NUL', 'COM1',
       'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2',
       'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9');
var I, L, Lw: Integer;
begin
 Result:= False;
 L:= Length(AFileName);
 if (L = 0) or (L > 255) or (AFileName[L] = '.') or (AFileName[L] = ' ') then Exit;
 for I:= 1 to L do 
  if (Ord(AFileName[I]) <= 255) and (AnsiChar(AFileName[I]) in InvalidFileNameChars) then Exit;
 AFileName:= UpperCase(AFileName);
 for I:= 0 to 21 do begin
  Lw:= Length(InvalidWords[I]);
  if (Pos(InvalidWords[I], AFileName) = 1) and
   ((Lw = Length(AFileName)) or (AFileName[Lw+1] = '.')) then Exit;
 end;
 Result:= True;
end;

【讨论】:

    【解决方案2】:

    据我所知,没有 RTL 或 Windows API 函数来验证文件名,因此您必须在 Windows File Naming Conventions 之后编写自己的函数:

    以下基本规则启用 创建和处理的应用程序 文件和目录的有效名称, 无论文件系统如何:

    • 在目录或文件的名称中使用句点将基本文件名与扩展名分开。
    • 使用反斜杠 () 分隔路径的组成部分。反斜杠将文件名与其路径分开,并将一个目录名与路径中的另一个目录名分开。您不能在实际文件或目录的名称中使用反斜杠,因为它是将名称分隔为组件的保留字符。
    • 根据需要使用反斜杠作为卷名的一部分,例如,“C:\path\file”中的“C:\”或“\server\share\path\file”中的“\server\share” " 用于通用命名约定 (UNC) 名称。有关 UNC 名称的详细信息,请参阅最大路径长度限制部分。
    • 不要假设区分大小写。例如,认为名称 OSCAR、Oscar 和 oscar 是相同的,即使某些文件系统(例如符合 POSIX 的文件系统)可能认为它们不同。请注意,NTFS 支持区分大小写的 POSIX 语义,但这不是默认行为。有关详细信息,请参阅创建文件。
    • 卷指示符(驱动器号)同样不区分大小写。例如,“D:\”和“d:\”指的是同一个卷。
    • 使用当前代码页中的任何字符作为名称,包括 Unicode 字符和扩展字符集 (128–255) 中的字符,但以下字符除外:

      • 以下保留字符:
        • >(大于)
        • :(冒号)
        • "(双引号)
        • /(正斜杠)
        • \(反斜杠)
        • | (竖条或竖管)
        • ? (问号)
        • *(星号)
      • 整数值零,有时称为 ASCII NUL 字符。
      • 整数表示在 1 到 31 范围内的字符,允许使用这些字符的备用流除外。有关文件流的更多信息,请参阅文件流。
      • 目标文件系统不允许的任何其他字符。
    • 在路径中使用句点作为目录组件来表示当前目录,例如“.\temp.txt”。有关详细信息,请参阅路径。
    • 在路径中使用两个连续的句点 (..) 作为目录组件来表示当前目录的父目录,例如“..\temp.txt”。有关详细信息,请参阅路径。
    • 不要使用以下保留的设备名称作为文件名:

      CON、PRN、AUX、NUL、COM1、COM2、COM3、COM4、COM5、COM6、COM7、COM8、COM9、LPT1、LPT2、LPT3、LPT4、LPT5、LPT6、LPT7、LPT8 和 LPT9。还要避免这些名称后跟扩展名;例如,不推荐使用 NUL.txt。有关详细信息,请参阅命名空间。

    • 不要以空格或句点结束文件或目录名称。尽管底层文件系统可能支持此类名称,但 Windows shell 和用户界面不支持。但是,可以将句点指定为名称的第一个字符。例如,“.temp”。

    您可以查看这篇 C++ 文章 Validating file names 以获取用于验证 Windows 文件名的完整示例函数。

    【讨论】:

      【解决方案3】:

      你也可以使用SysUtils.CharInSet函数:

      function isFileNameValid(fileNameToCheck : String) : Boolean;
      var
        invalidChars : Boolean;
        ch : Char;
      begin
        invalidChars := False;
        for ch in fileNameToCheck do begin
          invalidChars := SysUtils.CharInSet(ch, ['\', '/', ':', '*', '?', '"', '<', '>', '|']);
          if invalidChars then
            Break;
        end;
        Result := not invalidChars;
      end;
      

      【讨论】:

        【解决方案4】:

        所以,这个问题有点老了,但如果您正在寻找:对于 XE 和更新版本,您可以使用 System.IOUtils 中的 TPath 类。

        Result := TPath.HasValidFileNameChars(AFileName, UseWildcards); 
        

        【讨论】:

        • System.IOUtils 不可靠。我不知道 HasValidFileNameChars 但 HasValidPathChars 不起作用 - 或者至少不足以检查路径是否有效。如果您将“etc”作为参数传递给 HasValidPathChars,它将返回 true。因此,您还必须使用 TPath.HasPathValidColon 之类的东西(不幸的是,它不是公共的,而是私有的)。
        • TPath.HasValidFileNameChars('c:\IOUtils_sucks.txt', false) 将失败!!!!!!!!!!!!!!!!!!!
        • 根据 David Heffernan 的分析(stackoverflow.com/a/45347107/133516),TPath.HasValidFileNameChars 不可靠
        • @Z80 它失败了,因为那不是一个有效的文件名。它是 PATH 的名称。该函数只需要文件名部分。
        • @MarusNebunu - 我明白了。我希望能够在“现实生活”文件名(真实文件名+其路径)上使用该功能。因此,使用必须将完整的文件名拆分为文件名和路径并使用 HasValidPathChars + HasValidFileNameChars
        【解决方案5】:

        这太粗鲁了吗,梅森?

        function CanCreateFile(const FileName: string): Boolean;
        var
          H: THandle;
        begin
          H := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil,
               CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
        
          Result := H <> INVALID_HANDLE_VALUE;
        
          DeleteFile(FileName);
        end;
        

        【讨论】:

        • 我想这可能行得通,如果我打电话给or FileExists(filename)
        • @Mason Wheeler:如果文件存在,那么 CanCreateFile(FileName) 或 FileExists(FileName) 将删除现有文件(!)并返回 false。如果您使用 FileExists(FileName) 或 CanCreateFile(FileName),则现有文件仍将存在,结果将为 true。我建议不要依赖惰性布尔表达式(即项目选项中未选中的“完整布尔评估”),而是使用额外的 if ... then。
        • 您的代码有错误,顺便说一句。您不能将 FileName 定义为变量,因为它已经是参数的名称。删除变量,并改用 PChar(FileName)。
        【解决方案6】:

        我目前无法使用 Delphi 编译器,但您可以尝试

        function IsValidFilename(const FileName: string): boolean;
        begin
          result := DirectoryExists(ExtractFilePath(FileName)) and TPath.HasValidFileNameChars(ExtractFileName(FileName), false);
        end;
        

        【讨论】:

        • 也许您想在尚未创建的文件夹中创建文件。所以,directoryexists 会失败。
        【解决方案7】:

        试试这段代码(取自delphi faq

        const
          { for short 8.3 file names }
          ShortForbiddenChars : set of Char = [';', '=', '+', '<', '>', '|',
                                               '"', '[', ']', '\', '/', ''''];
          { for long file names }
          LongForbiddenChars  : set of Char = ['<', '>', '|', '"', '\', '/', ':', '*', '?'];
        
        function TestFilename(Filename: String; islong: Boolean) : Boolean;
        var
          I: integer;
        begin
          Result := Filename <> '';
          if islong then
          begin
            for I := 1 to Length(Filename) do
              Result := Result and not (Filename[I] in LongForbiddenChars);
          end
          else
          begin
            for I := 1 to Length(Filename) do
              Result := Result and not (Filename[I] in ShortForbiddenChars);
          end;
        end;
        

        【讨论】:

        • begin...end 是多余的。另外,您应该制作isLong: boolean = true(或完全删除 8.3 代码)。
        • 代码不测试禁止的文件名,如 con、prn、aux
        • 它应该带有目录验证
        • 代码也不测试文件名中的“..”
        【解决方案8】:

        检查目录是否存在:

        在 SysUtils 中,您有:DirectoryExists

        检查文件名:

        无效的文件名字符是:\ / : * ? " &lt; &gt; | 所以你可以像这样检查文件名:

        for c in AFileName do
        begin
          OK := NOT (C in ['\', '/', ':', '*', '?', '"', '<', '>', '|']);
          if not OK then Break;
        end;
        

        检查文件夹是否可写:

        重复: How can I use Delphi to test if a Directory is writeable?

        【讨论】:

          【解决方案9】:

          要查看它是否存在,请使用DirectoryExists()FileExists()。要查看是否可以创建/编辑文件,请使用 FileOpen() 和 ReadWrite 并查看它是否成功。

          【讨论】:

            猜你喜欢
            • 2021-07-20
            • 2021-10-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-22
            • 1970-01-01
            相关资源
            最近更新 更多