robertjohns 0 Posted May 24, 2023 (edited) 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 May 24, 2023 by robertjohns Share this post Link to post
Lajos Juhász 295 Posted May 24, 2023 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
Fr0sT.Brutal 900 Posted May 24, 2023 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
Lajos Juhász 295 Posted May 24, 2023 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
robertjohns 0 Posted May 24, 2023 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
mvanrijnen 123 Posted May 24, 2023 (edited) Why not use (in unit System.IOUtils), TDirectory.GetFiles(), see: System.IOUtils.TDirectory.GetFiles - RAD Studio API Documentation (embarcadero.com) (i think it's better to use those functions because you do not know how to use findfirst/next etc correctly) Edited May 24, 2023 by mvanrijnen 1 Share this post Link to post
robertjohns 0 Posted May 24, 2023 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
mvanrijnen 123 Posted May 24, 2023 (edited) 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 May 24, 2023 by mvanrijnen Share this post Link to post
robertjohns 0 Posted May 24, 2023 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
mvanrijnen 123 Posted May 24, 2023 (edited) 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 May 24, 2023 by mvanrijnen Share this post Link to post
programmerdelphi2k 237 Posted May 24, 2023 @robertjohns you can test if is a Directory using LIsDirectory := ((FileGetAttr(LFilename) and faDirectory) = faDirectory); Share this post Link to post
mvanrijnen 123 Posted May 24, 2023 and if you stay with the findfirst/findnext solution, do not forget the findclose 🙂 Share this post Link to post
robertjohns 0 Posted May 24, 2023 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
programmerdelphi2k 237 Posted May 24, 2023 (edited) @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 May 24, 2023 by programmerdelphi2k Share this post Link to post
Remy Lebeau 1436 Posted May 24, 2023 (edited) 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 May 24, 2023 by Remy Lebeau 3 Share this post Link to post
mvanrijnen 123 Posted May 24, 2023 @programmerdelphi2k I believe the findclose needs to be executed when the findfirst succeeds, otherwise not. Share this post Link to post
programmerdelphi2k 237 Posted May 24, 2023 8 minutes ago, mvanrijnen said: @programmerdelphi2k I believe the findclose needs to be executed when the findfirst succeeds, otherwise not. for sure, just do it as expected... 😉 Share this post Link to post
robertjohns 0 Posted May 25, 2023 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
programmerdelphi2k 237 Posted May 25, 2023 (edited) 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 May 25, 2023 by programmerdelphi2k Share this post Link to post
Sherlock 663 Posted May 25, 2023 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
programmerdelphi2k 237 Posted May 25, 2023 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
Sherlock 663 Posted May 25, 2023 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
robertjohns 0 Posted May 25, 2023 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
programmerdelphi2k 237 Posted May 25, 2023 (edited) 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 May 25, 2023 by programmerdelphi2k Share this post Link to post