Jump to content
robertjohns

File Search

Recommended Posts

I am using Delphi 10.2

 

Is it possible to search Directories and Sub-Directories by File Name instead File Mask like

 

mytext.txt instead of *.txt

 

So far I tried the following function but it does not work by filename it works with file extension

 

another issue with this function is that it does not list the complete searched files from Windows\system32\drivers\

 

procedure FindFilePattern(root:String;pattern:String);
var
  SR:TSearchRec;
begin
  root:=IncludeTrailingPathDelimiter(root);
  if FindFirst(root+'*',faAnyFile,SR) = 0 then
  begin
      repeat
          Application.ProcessMessages;
          if ((SR.Attr and faDirectory) = SR.Attr ) and (pos('.',SR.Name)=0) then
             FindFilePattern(root+SR.Name,pattern)
          else
          begin
           if pos(pattern,SR.Name)>0 then Form1.ListBox1.Items.Add(Root+SR.Name);
          end;
      until FindNext(SR)<>0;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FindFilePattern('C:\','.sys');
end;

 

Edited by robertjohns

Share this post


Link to post

Your code is working for me.  A note that you're leaking memory findfirst will allocate some memory and that must be released with FindClose(SR). Also you should never write Form1.Listbox1.

Share this post


Link to post
2 hours ago, robertjohns said:

the following function

Has too many mistakes. Even ChatGPT would write it better. Why not just do FindFirst(root+pattern) where pattern could be either mask or exact name?

Share this post


Link to post
10 minutes ago, Fr0sT.Brutal said:

Why not just do FindFirst(root+pattern) where pattern could be either mask or exact name?

One possible reasould could be that FindFirst would not return subfolders that doesn't match the pattern.

 

If agree there are bugs for example (SR.Attr and faDirectory) = SR.Attr should be (SR.Attr and faDirectory) = faDirectory

Share this post


Link to post
2 hours ago, Fr0sT.Brutal said:

Has too many mistakes. Even ChatGPT would write it better. Why not just do FindFirst(root+pattern) where pattern could be either mask or exact name?

Thanks for your reply .. but I have already tried all these method none worked to search for direct file name

Share this post


Link to post
2 hours ago, Lajos Juhász said:

One possible reasould could be that FindFirst would not return subfolders that doesn't match the pattern.

 

If agree there are bugs for example (SR.Attr and faDirectory) = SR.Attr should be (SR.Attr and faDirectory) = faDirectory

I have tried changing SR.Attr to faDirectory too the result is always empty

Share this post


Link to post

The "ChatGPT" solution:

 

Question:

Quote

write a find file with pattern method in delphi

 

 


uses
  System.SysUtils, System.IOUtils;

procedure FindFilesWithPattern(const directoryPath, filePattern: string);
var
  files: TStringDynArray;
  fileName: string;
begin
  try
    files := TDirectory.GetFiles(directoryPath, filePattern);
    for fileName in files do
      Writeln(fileName);
  except
    on E: Exception do
      Writeln('An error occurred: ' + E.Message);
  end;
end;

 

Explanation from ChatGPT:

Quote

This method takes two parameters:

 

  • directoryPath represents the path of the directory where the search is performed.
  • filePattern represents the pattern to match the files against. The pattern can include wildcard characters such as * (matches any characters) and ? (matches a single character).

Inside the method, TDirectory.GetFiles function from the System.IOUtils unit is used to retrieve an array of file names that match the specified pattern in the given directory. It throws an exception if any error occurs during the search.

The returned array of file names is then iterated using a for-in loop, and each file name is printed using Writeln. You can modify this part to perform any desired action with the matched file names.

Note: Make sure you have the System.SysUtils and System.IOUtils units included in your uses clause for the code to work correctly.

 

Edited by mvanrijnen

Share this post


Link to post
31 minutes ago, mvanrijnen said:

The "ChatGPT" solution:

 

Question:

 


uses
  System.SysUtils, System.IOUtils;

procedure FindFilesWithPattern(const directoryPath, filePattern: string);
var
  files: TStringDynArray;
  fileName: string;
begin
  try
    files := TDirectory.GetFiles(directoryPath, filePattern);
    for fileName in files do
      Writeln(fileName);
  except
    on E: Exception do
      Writeln('An error occurred: ' + E.Message);
  end;
end;

 

Explanation from ChatGPT:

What is

files: TStringDynArray;

 

and how to loop different file name ?

Share this post


Link to post

TStringDynArray, i believe its defined in the unit "System.Types".

 

you call this function for example with:

FindFilesWithPatttern('C:\MyFolder', '*.sys');            // get all the .sys files

FindFilesWithPatttern('C:\MyFolder', 'myfile.txt');   // get the myfile.txt file(s)

(maybe for your example u should use an overload of the TDIrectory.GetFiles method which also recurs over the (sub)folders.
 

 

the line:

 files := TDirectory.GetFiles(directoryPath, filePattern);

you change in:

 

 files := TDirectory.GetFiles(directoryPath, filePattern, TSearchOption.soAllDirectories);

for that.

 

As said before by someone else, a filemask can also be a full filename. 

 

Edited by mvanrijnen

Share this post


Link to post
8 hours ago, robertjohns said:

I am using Delphi 10.2

 

Is it possible to search Directories and Sub-Directories by File Name instead File Mask like

 

mytext.txt instead of *.txt

 

So far I tried the following function but it does not work by filename it works with file extension

 

another issue with this function is that it does not list the complete searched files from Windows\system32\drivers\

 


procedure FindFilePattern(root:String;pattern:String);
var
  SR:TSearchRec;
begin
  root:=IncludeTrailingPathDelimiter(root);
  if FindFirst(root+'*',faAnyFile,SR) = 0 then
  begin
      repeat
          Application.ProcessMessages;
          if ((SR.Attr and faDirectory) = faDirectory ) and (pos('.',SR.Name)=0) then
             FindFilePattern(root+SR.Name,pattern)
          else
          begin
           if pos(pattern,SR.Name)>0 then Form1.ListBox1.Items.Add(Root+SR.Name);
          end;
      until FindNext(SR)<>0;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FindFilePattern('C:\','.sys');
end;

 

Function I have mentioned in my first post also works well I just need to find any modification in it to search by filename instead of filemask

 

Even I tried if FindFirst(root+pattern,faAnyFile,SR) = 0 then .. but not working

Share this post


Link to post

@robertjohns

 

try this:

implementation

{$R *.dfm}

procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: string; const AListBox: TListBox);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  LContinue := FindFirst(AFolderRoot + '\' + APatternToSearch, faAnyFile, LSearchRecord) = 0;
  try
    //
    while LContinue do
      begin
        LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
        //
        if (LSearchRecord.Name <= '..') then
          begin
            LContinue := FindNext(LSearchRecord) = 0;
            //
            continue;
          end;
        //
        if LIsDirectory then
          AListBox.Items.Add('Dir: ' + LSearchRecord.Name)
        else
          AListBox.Items.Add('File: ' + LSearchRecord.Name);
        //
        LContinue := FindNext(LSearchRecord) = 0;
      end;
  finally
    FindClose(LSearchRecord);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Items.Clear;
  //
  ListBox1.Items.Add(' ------ *.* ------');
  //
  MyFindFilePattern('D:\MyZip', '*.*', ListBox1);
  //
  ListBox1.Items.Add(' ------ complete filename ------');
  //
  MyFindFilePattern('D:\MyZip', 'MyNewZip.zip', ListBox1);
end;

 

Edited by programmerdelphi2k

Share this post


Link to post
59 minutes ago, programmerdelphi2k said:

  LContinue := FindFirst(AFolderRoot + '\' + APatternToSearch, faAnyFile, LSearchRecord) = 0;

 

If the root path already has a '\' on the end of it, you would be adding another '\' unnecessarily.  Better to use IncludeTrailingPathDelimiter() instead, eg:

LContinue := FindFirst(IncludeTrailingPathDelimiter(AFolderRoot) + APatternToSearch, faAnyFile, LSearchRecord) = 0;
Quote

if (LSearchRecord.Name <= '..') then

 

That comparison wont work correctly if the file name begins with any character that is lexicographically less-than '.' (a few of them are reserved, but many of them are not), namely:

! # $ % & ' ( ) + , -

You need to check for '.' and '..' specifically instead, eg:

if (LSearchRecord.Name = '.') or (LSearchRecord.Name = '..') then
Edited by Remy Lebeau
  • Like 3

Share this post


Link to post

How can I declare and use Array of Strings instead of pattern : String in below function  and then how to call it with array of strings ?

 

procedure FindFilePattern(root:String;pattern:String);
var
  SR:TSearchRec;
begin
  root:=IncludeTrailingPathDelimiter(root);
  if FindFirst(root+'*',faAnyFile,SR) = 0 then
  begin
      repeat
          Application.ProcessMessages;
          if ((SR.Attr and faDirectory) = faDirectory ) and (pos('.',SR.Name)=0) then
             FindFilePattern(root+SR.Name,pattern)
          else
          begin
           if pos(pattern,SR.Name)>0 then Form1.ListBox1.Items.Add(Root+SR.Name);
          end;
      until FindNext(SR)<>0;
  end;
end;

Share this post


Link to post
procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string; const AListBox: TListBox);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  for var LPattern in APatternToSearch do
    begin
      LContinue := FindFirst(IncludeTrailingPathDelimiter(AFolderRoot) + LPattern, faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    AListBox.Items.Add('Dir: ' + LSearchRecord.Name)
                  else
                    AListBox.Items.Add('File: ' + LSearchRecord.Name);
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyFindFilePattern('d:\MyZip', ['*.zip', '*.txt'], ListBox1);
end;

 

Edited by programmerdelphi2k

Share this post


Link to post

Why would anyone choose to limit reusability of a method clearly screaming for reusability by arbitrarily dropping a TListbox into the parameter list? TStringlist as a result for a function is just fine, and that can be used to fill TListbox.Items without major surgery. You whip out source code faster than I can think @programmerdelphi2k, but sometimes slowing down and perhaps not propagate code smells to obvious newcomers, seems a wiser choice.

Share this post


Link to post
On 5/24/2023 at 5:27 AM, robertjohns said:

if pos(pattern,SR.Name)>0 then Form1.ListBox1.Items.Add(Root+SR.Name);

I only reprodure that "asker" have showed in your post... if you want use any other "container", you're free for that!

Share this post


Link to post

I am not blind, I know what he asked. But to just come up with code without perhaps improving the overall style or at least pointing out what is wrong with it just seems careless. But do as you please, if it makes you happy.

Share this post


Link to post
6 hours ago, robertjohns said:

How can I declare and use Array of Strings instead of pattern : String in below function  and then how to call it with array of strings ?

 


procedure FindFilePattern(root:String;pattern:String);
var
  SR:TSearchRec;
begin
  root:=IncludeTrailingPathDelimiter(root);
  if FindFirst(root+'*',faAnyFile,SR) = 0 then
  begin
      repeat
          Application.ProcessMessages;
          if ((SR.Attr and faDirectory) = faDirectory ) and (pos('.',SR.Name)=0) then
             FindFilePattern(root+SR.Name,pattern)
          else
          begin
           if pos(pattern,SR.Name)>0 then Form1.ListBox1.Items.Add(Root+SR.Name);
          end;
      until FindNext(SR)<>0;
  end;
end;

Possible to do the same with this function ?

Share this post


Link to post
41 minutes ago, robertjohns said:

Possible to do the same with this function ?

just look my sample and use a "FOR"...looping to catch each element from arrary... but you are doing a "recursive called"... then needs some adjusts

dont forget: you needs add a FindClose(... sr ) in your code

Edited by programmerdelphi2k

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×