FindFirst 和问号

FindFirst and question mark

我需要删除所有名称以 "a" 开头的文件,然后是三个任意字母和“.txt”扩展名,如 "a123.txt"。这是代码:

var
  sFileMask: string;
  tsrMessage: TSearchRec;
begin
  sFileMask := 'c:/a???.txt';
  if SysUtils.FindFirst(sFileMask, 0, tsrMessage) = 0 then
  begin
    repeat
      ShowMessage(tsrMessage.Name);
    until FindNext(tsrMessage) <> 0;
    SysUtils.FindClose(tsrMessage);
  end;
end;

一直以为问号代表一个字符,没想到发现这段代码returns "a.txt", "a1.txt" and "a123.txt" 文件名字。有没有一种简单的方法可以修改代码以使其仅查找 "a123.txt" 之类的文件?

此行为符合设计。 Raymond Chen 在这里解释:How did wildcards work in MS-DOS?

您将在命令解释器中看到完全相同的行为。

C:\Desktop>dir a???.txt
 Volume in drive C has no label.
 Volume Serial Number is 20DA-7FEB

 Directory of C:\Desktop

26/06/2016  14:03                 6 a.txt
26/06/2016  14:03                 6 a1.txt
26/06/2016  14:03                 6 a12.txt
26/06/2016  14:03                 6 a123.txt
               4 File(s)             24 bytes
               0 Dir(s)  286,381,445,120 bytes free

无法说服 FindFirstFile(Windows 上 RTL FindFirst 后面的 API)按您希望的方式行事。您最好的选择是枚举整个目录,并使用您选择的模式匹配算法执行您自己的过滤。

满足您特定需求的最简单解决方案是替换此:

ShowMessage(tsrMessage.Name);

有了这个

if length(tsrMessage.Name)=8 then ShowMessage(tsrMessage.Name);

这将确保文件名的长度恰好是四个字符+句点+扩展名。就像 David 所说的那样,没有办法让 API 进行这种过滤,所以你必须自己做,但在你的特定情况下,没有必要枚举整个目录。您至少可以让 API 执行 可以 执行的过滤,然后在其上执行您自己的过滤。

编辑:如果你需要确保"a"后面的三个字符是数字,你可以这样做:

if (length(tsrMessage.Name)=8) and tsrMessage[2].IsDigit and tsrMessage[3].IsDigit and tsrMessage[4].IsDigit then ShowMessage(tsrMessage.Name);

前提是您使用的是现代编译器(您需要包含 "Characters" 单元)。另请注意,如果您正在编译移动版本,则需要改用索引 [1]、[2] 和 [3],因为它们的字符串索引从 0 开始。

如果您使用的是旧版本,您可以这样做:

function IsDigit(c : char) : boolean;
  begin
    Result:=(c>='0') and (c<='9')
  end;

if (length(tsrMessage.Name)=8) and IsDigit(tsrMessage[2]) and IsDigit(tsrMessage[3]) and IsDigit(tsrMessage[4]) then ShowMessage(tsrMessage.Name);