0

I need to delete all files, which names are started with "a", then three arbitrary letters and ".txt" extension like "a123.txt". Here is the code:

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;

I always thought that the question mark means one and only one character, but to my surprise found that this code returns "a.txt", "a1.txt" and "a123.txt" file names. Is there a simple way to modify the code for it to seek only files like "a123.txt"?

Molochnik
  • 704
  • 5
  • 23

2 Answers2

4

This behaviour is as designed. It is explained by Raymond Chen here: How did wildcards work in MS-DOS?

You will see the exact same behaviour from the command interpreter.

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

There is no way to persuade FindFirstFile (the API that is behind the RTL's FindFirst on Windows) to behave the way you wish. Your best option is to enumerate the entire directory, and perform your own filtering using your chosen pattern matching algorithm.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks, got it, it's been long time since I last used this wildcard so i was 100% sure that "?" means exactly one character. – Molochnik Jun 26 '16 at 16:24
  • @Molochnik The single character wildcard in SQL works as you are describing - maybe that's where your confusion is coming from. Its caught me before now. Regards, – Michael Vincent Jun 27 '16 at 12:57
  • @Michael Yes, that's possible, but unlikely, for a decade I've been using regular-expressions notation (earlier I used fnmatch) and it didn't come into my mind that there still exist regulars without a wildcard meaning a single character. – Molochnik Jun 27 '16 at 14:02
4

The simplest solution for your specific need is to replace this:

ShowMessage(tsrMessage.Name);

with this

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

this will ensure that the length of the file name is exactly four characters + the period + the extension. Like David says, there's no way to have the API do this kind of filtering, so you'll have to do it yourself, but in your particular case, there's no need to enumerate the entire directory. You may at least let the API do the filtering it can do, and then do your own filtering on top of it.

EDIT: If you need to ensure that the three characters following the "a" are digits, you can do it this way:

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

provided you are using a modern compiler (you'll need to include the "Characters" unit). Also take note that if you are compiling a mobile version, you'll need to use index [1], [2] and [3] instead, as they start index at 0 for strings.

If you are using an older version, you can do it like this:

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);
HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • Yes I also wanted to use this method but the example I used was simplified. Actually I need to delete files according to mask as in Format like a%03d.txt etc. In this case the code becomes too complicated for such a simple task so I decided that I was doing something wrong. – Molochnik Jun 26 '16 at 16:31