Jump to content
Sign in to follow this  
destro

Recursive Folder Search Function

Recommended Posts

destro

Hi all,

I've been trying to get my head round Functions and decided to try to modify some code to do some recursive folder searching for a particular file.

The code takes a file to search for and a root directory and searches for the file and returns the full path.

Here's the code:

Dim $SearchFile = "bod_r.TTF"
Dim $StartDir = @ProgramFilesDir
Dim $SearchResult = 0

$FilePath = FileSearch($SearchFile, $StartDir)

MsgBox(0,"FilePath",$FilePath)

Exit

Func FileSearch($SearchFile, $StartDir, $Depth = 0)

If $Depth = 0 Then Global $RFString = ""

    $Search = FileFindFirstFile($StartDir & "\*.*")
    If @error Then Return

    ;Search through all files and folders in directory
    While $SearchResult = 0
        $Next = FileFindNextFile($Search)
        If @error Then ExitLoop

        ;If folder, recurse
        If StringInStr(FileGetAttrib($StartDir & "\" & $Next), "D") Then
            FileSearch($SearchFile, $StartDir & "\" & $Next, $Depth + 1)
        Else
            ;Append filename to master string
            $RFString = $StartDir & "\" & $Next
        EndIf
        If $Next = $SearchFile Then
            $SearchResult = 1
        EndIf
    WEnd
    FileClose($Search)
    If $SearchResult = 1 Then
        Return $RFString
    Else
        Return "File Not Found"
    EndIf
EndFunc   ;==>FileSearch

The problem is that I can't seem to work out a way to use the function without declaring the $SearchResult = 0 first. This is annoying as it means the Function is not self contained. The $SearchResult is used to keep the While loop alive and also to determine what is returned. If I Dim it inside the fuction then it breaks it (Returns File Not Found). Any more experinced people got any idea how I can make this work so it's self contained so i can start to build my own #include file.

Thanks

Edited by destro

Share this post


Link to post
Share on other sites
TurionAltec

If you declare it as a global variable inside the function:

Global $SearchResult = 0

Then it seems to work. I haven't go the foggiest idea why it doesn't work as a local variable.

To recursively find a file, I'd tend to use (Dir /s/B )

Edited by TurionAltec

Share this post


Link to post
Share on other sites
martin

Hi all,

I've been trying to get my head round Functions and decided to try to modify some code to do some recursive folder searching for a particular file.

The code takes a file to search for and a root directory and searches for the file and returns the full path.

Here's the code:

Dim $SearchFile = "bod_r.TTF"
Dim $StartDir = @ProgramFilesDir
Dim $SearchResult = 0

$FilePath = FileSearch($SearchFile, $StartDir)

MsgBox(0,"FilePath",$FilePath)

Exit

Func FileSearch($SearchFile, $StartDir, $Depth = 0)

If $Depth = 0 Then Global $RFString = ""

    $Search = FileFindFirstFile($StartDir & "\*.*")
    If @error Then Return

    ;Search through all files and folders in directory
    While $SearchResult = 0
        $Next = FileFindNextFile($Search)
        If @error Then ExitLoop

        ;If folder, recurse
        If StringInStr(FileGetAttrib($StartDir & "\" & $Next), "D") Then
            FileSearch($SearchFile, $StartDir & "\" & $Next, $Depth + 1)
        Else
            ;Append filename to master string
            $RFString = $StartDir & "\" & $Next
        EndIf
        If $Next = $SearchFile Then
            $SearchResult = 1
        EndIf
    WEnd
    FileClose($Search)
    If $SearchResult = 1 Then
        Return $RFString
    Else
        Return "File Not Found"
    EndIf
EndFunc   ;==>FileSearch

The problem is that I can't seem to work out a way to use the function without declaring the $SearchResult = 0 first. This is annoying as it means the Function is not self contained. The $SearchResult is used to keep the While loop alive and also to determine what is returned. If I Dim it inside the fuction then it breaks it (Returns File Not Found). Any more experinced people got any idea how I can make this work so it's self contained so i can start to build my own #include file.

Thanks

I haven't tested this but it might work.

Dim $SearchFile = "bod_r.TTF"
Dim $StartDir = @ProgramFilesDir
;Dim $SearchResult = 0

$FilePath = FileSearch($SearchFile, $StartDir)

MsgBox(0, "FilePath", $FilePath)

Exit

Func FileSearch($SearchFile, $StartDir);, $Depth = 0)
    ;If $Depth = 0 Then Global 
    Local $Search, $RFString = "File not found"

    $Search = FileFindFirstFile($StartDir & "\*.*")
    If @error Then Return $RFString 

    ;Search through all files and folders in directory
    While $Refstring = "File not found"
        $Next = FileFindNextFile($Search)
        If @error Then ExitLoop

        ;If folder, recurse
        If StringInStr(FileGetAttrib($StartDir & "\" & $Next), "D") Then
            $RFString = FileSearch($SearchFile, $StartDir & "\" & $Next);, $Depth + 1)
        Else
            if $Next = $SearchFile then $RFString = $StartDir
        EndIf
       
    WEnd
    FileClose($Search)
    
    Return $RFString
    
EndFunc   ;==>FileSearch

You can also pass variables by reference so that avoids needing a global variable inside your function. You could also have the function return an array, so you could make one element the file path, another element some other value.

EDIT:Corrected return in one place.

Edited by martin

Serial port communications UDF Includes functions for binary transmission and reception.printing UDF Useful for graphs, forms, labels, reports etc.Add User Call Tips to SciTE for functions in UDFs not included with AutoIt and for your own scripts.Functions with parameters in OnEvent mode and for Hot Keys One function replaces GuiSetOnEvent, GuiCtrlSetOnEvent and HotKeySet.UDF IsConnected2 for notification of status of connected state of many urls or IPs, without slowing the script.

Share this post


Link to post
Share on other sites
zodiac

I use this and it works great:

#include<file.au3>

_search("C:\")

Func _search($dir)
    Local $ArrTargetItems, $TargetItem
    If (StringRight($dir, 1) = "\") Then $dir = StringTrimRight($dir, 1)
    $ArrTargetItems = _FileListToArray($dir, "*", 0)
    If IsArray($ArrTargetItems) Then
        For $n = 1 To $ArrTargetItems[0]
            $TargetItem = $dir & '\' & $ArrTargetItems[$n]
            If StringInStr(FileGetAttrib($TargetItem), "D") Then ;This is a folder
                _search($TargetItem) ;Call recursively
            Else ;This is a file
                ;Do whatever you want to do with the file here.
                ;$TargetItem contains the file full path
                ;$ArrTargetItems[$n] contains the file name
                MsgBox(0, $ArrTargetItems[$n], $TargetItem)
            EndIf
        Next
    EndIf
EndFunc   ;==>_search

Tested on volumes with 2 Terabytes and didn't skip a beat.

Share this post


Link to post
Share on other sites
destro

Thanks very much to all who came up with ideas for this. I've kinda scrapped my original function although it was very useful as a learning exercise (my first one!) as Zodiac provided a very elegant solution to the problem.

Next on the list, recursive registry searching...

Share this post


Link to post
Share on other sites
ironmankho

Your code is awesome ..... it is possible it search only specific extension on c: drive like *.xls

Share this post


Link to post
Share on other sites
destro

Ok guys, I actually went back to my original code as Zodiac's script wasn't giving me quite what I wanted (I couldn't seem to Return the value out of the function that I was after).

I took on board the comments that you guys suggested and came up with the following code

Dim $SearchFile = "iexplore.exe"
Dim $StartDir = @ProgramFilesDir

$FilePath = FileSearch($SearchFile, $StartDir)

MsgBox(0, "FilePath", $FilePath)

Exit

Func FileSearch($SearchFile, $StartDir)
    Local $Search, $RFString = "File not found"
    $Search = FileFindFirstFile($StartDir & "\*.*")
    If @error Then Return $RFString
    ;Search through all files and folders in directory
    While $RFString = "File not found"
        $Next = FileFindNextFile($Search)
        If @error Then ExitLoop
        ;If folder, recurse
        If StringInStr(FileGetAttrib($StartDir & "\" & $Next), "D") Then
            $RFString = FileSearch($SearchFile, $StartDir & "\" & $Next)
        Else
            If $Next = $SearchFile Then $RFString = $StartDir & "\" & $Next
        EndIf
    WEnd
    FileClose($Search)
    Return $RFString
EndFunc   ;==>FileSearch

This works a treat and gives me exactly what I was after. Thanks again all

Share this post


Link to post
Share on other sites
ironmankho

can it is possible it search all hard drive partition ? to specific extension file like *.doc How ?

Share this post


Link to post
Share on other sites
omikron48

I made a search function some time ago using a stack. You need to control the size of the predeclared arrays if you want to trim down on the memory usage.

;Put somewhere at the start of your script
;### STACK DEFINITION ###
Global $RESULT_MAX = 0xFFF ;maximum size of the returned array
Global $STACK_MAX = 0xFFF ;maximum size of the search stack
Global $STACK[$STACK_MAX] ;the search stack
$STACK[0] = 0 ;stack element counter
Func _Push($var) ;Utility function. DO NOT CALL
    If $STACK[0] < $STACK_MAX - 1 Then
        $STACK[0] += 1
        $STACK[$STACK[0]] = $var
    Else
        Return 1
    EndIf
    Return 0
EndFunc
Func _Pop() ;Utility function. DO NOT CALL
    If $STACK[0] > 0 Then
        $STACK[0] -= 1
        Return $STACK[$STACK[0] + 1]
    Else
        SetError(1)
    EndIf
    Return 0
EndFunc
;### END STACK DEFINITION ###

;### SEARCH FUNCTION ###
;$path = start location of search
;$filter = expression to use for file matching (e.g. *.txt)
;$exclude = file attributes to exclude from search (e.g. if you want to skip system and/or hidden folders)
;$depth = folder depth to limit search
;         0  -> current folder only
;         n  -> search up to n folders deep
;         -1 -> search in all subfolders
;
;Returns array containing full path of matched files, with [0] containing the count of returned elements.
Func _Search($path, $filter, $exclude, $depth)
    Local $current = @WorkingDir
    If FileChangeDir($path) == 1 Then
        If StringCompare(StringRight($path, 1), "\") <> 0 Then
            $path &= "\"
        EndIf
        Local $array[2] = [$path, 0]
        _Push($array)
        Local $result = _SearchUtil($filter, $exclude, $depth)
        FileChangeDir($current)
        Return $result
    Else
        SetError(1)
    EndIf
    FileChangeDir($current)
    Local $empty[2] = [0, 0]
    Return $empty
EndFunc
Func _SearchUtil($filter, $exclude, $depth) ;Utility function. DO NOT CALL
    Local $result[$RESULT_MAX]
    Local $search, $fname, $attrib
    Local $array[2], $temp[2]
    $result[0] = 0
    While 1
        ;get top of stack
        $array = _Pop()
        If @error == 1 Then
            ExitLoop
        EndIf
        ;change directory
        FileChangeDir($array[0])
        ;search for matches in current directory
        $search = FileFindFirstFile($filter)
        While 1
            $fname = FileFindNextFile($search)
            If @error == 1 Then
                ExitLoop
            EndIf
            $attrib = FileGetAttrib($fname)
            If _IsIncluded($attrib, $exclude) Then
                $result[0] += 1
                $result[$result[0]] = $array[0] & $fname
            EndIf
        WEnd
        ;search subdirectory
        If $depth == -1 Or $array[1] < $depth Then
            $search = FileFindFirstFile("*")
            While 1
                $fname = FileFindNextFile($search)
                If @error == 1 Then
                    ExitLoop
                EndIf
                $attrib = FileGetAttrib($fname)
                If StringInStr($attrib, "D") <> 0 Then
                    If _IsIncluded($attrib, $exclude) Then
                        $temp[0] = $array[0] & $fname & "\"
                        $temp[1] = $array[1] + 1
                        _Push($temp)
                    EndIf
                EndIf
            WEnd
        EndIf
    WEnd
    Return $result
EndFunc
Func _IsIncluded($attrib, $exclude) ;Utility function. DO NOT CALL
    For $i = 1 To StringLen($exclude)
        If StringInStr($attrib, StringMid($exclude, $i, 1)) <> 0 Then
            Return False
        EndIf
    Next
    Return True
EndFunc
;### END SEARCH FUNCTION ###
Edited by omikron48

Share this post


Link to post
Share on other sites
PeterAtkin

@omikron48 this looks like just what I have been looking for but how do you use this as I tried various ways such as _Search("c:\", "*.rnd","a",0), or _Search("c:\", "autorun.*","",-1) and several other but I get no results even when I use a file i know is there.. also it not clear how one would use the exclude option..

Hope I am making sence..

Edited by PeterAtkin

[topic='115020'] AD Domain Logon Script[/topic]

Share this post


Link to post
Share on other sites
omikron48

The exclude parameter is used for filtering search and search results. If a file property value is included in the exclude string, the file is skipped. If a subdirectory property value is included in the exclude string, the subdirectory and its contents are are skipped from the search.

e.g. If the exclude string is "SH", the search will skip files and subdirectory branches that have either system or hidden set.

_Search("c:\", "*.rnd","a",0)

This will not return anything unless you do backups which clear the archive bit from your files and folders.

_Search("c:\", "autorun.*","",-1)

This should be fine if any file fits the "autorun.*" filter.

I just tested my code again with a folder setup like so:

C:\test\autorun.bat
C:\test\autorun.txt
C:\test\test.bat
C:\test\test.txt
C:\test\autorun\autorun.file
C:\test\autorun\test.file
C:\test\test\autorun.zip
C:\test\test\test.zip

This is the script I used for testing:

Opt("MustDeclareVars", 1)
;Put somewhere at the start of your script
;### STACK DEFINITION ###
Global $RESULT_MAX = 0xFFF ;maximum size of the returned array
Global $STACK_MAX = 0xFFF ;maximum size of the search stack
Global $STACK[$STACK_MAX] ;the search stack
$STACK[0] = 0 ;stack element counter
Func _Push($var) ;Utility function. DO NOT CALL
    If $STACK[0] < $STACK_MAX - 1 Then
        $STACK[0] += 1
        $STACK[$STACK[0]] = $var
    Else
        Return 1
    EndIf
    Return 0
EndFunc
Func _Pop() ;Utility function. DO NOT CALL
    If $STACK[0] > 0 Then
        $STACK[0] -= 1
        Return $STACK[$STACK[0] + 1]
    Else
        SetError(1)
    EndIf
    Return 0
EndFunc
;### END STACK DEFINITION ###

;### TEST SCRIPT ###
Global $array = _Search("c:\test", "autorun.*","",-1)
Global $display = ""

For $i = 1 To $array[0]
    $display &= $array[$i] & @CRLF
Next

MsgBox(0x2000, "Search Results: " & $array[0], $display)
;### TEST SCRIPT ###

;### SEARCH FUNCTION ###
;$path = start location of search
;$filter = expression to use for file matching (e.g. *.txt)
;$exclude = file attributes to exclude from search (e.g. if you want to skip system and/or hidden folders)
;$depth = folder depth to limit search
;         0  -> current folder only
;         n  -> search up to n folders deep
;         -1 -> search in all subfolders
;
;Returns array containing full path of matched files, with [0] containing the count of returned elements.
Func _Search($path, $filter, $exclude, $depth)
    Local $current = @WorkingDir
    If FileChangeDir($path) == 1 Then
        If StringCompare(StringRight($path, 1), "\") <> 0 Then
            $path &= "\"
        EndIf
        Local $array[2] = [$path, 0]
        _Push($array)
        Local $result = _SearchUtil($filter, $exclude, $depth)
        FileChangeDir($current)
        Return $result
    Else
        SetError(1)
    EndIf
    FileChangeDir($current)
    Local $empty[2] = [0, 0]
    Return $empty
EndFunc
Func _SearchUtil($filter, $exclude, $depth) ;Utility function. DO NOT CALL
    Local $result[$RESULT_MAX]
    Local $search, $fname, $attrib
    Local $array[2], $temp[2]
    $result[0] = 0
    While 1
        ;get top of stack
        $array = _Pop()
        If @error == 1 Then
            ExitLoop
        EndIf
        ;change directory
        FileChangeDir($array[0])
        ;search for matches in current directory
        $search = FileFindFirstFile($filter)
        While 1
            $fname = FileFindNextFile($search)
            If @error == 1 Then
                ExitLoop
            EndIf
            $attrib = FileGetAttrib($fname)
            If _IsIncluded($attrib, $exclude) Then
                $result[0] += 1
                $result[$result[0]] = $array[0] & $fname
            EndIf
        WEnd
        ;search subdirectory
        If $depth == -1 Or $array[1] < $depth Then
            $search = FileFindFirstFile("*")
            While 1
                $fname = FileFindNextFile($search)
                If @error == 1 Then
                    ExitLoop
                EndIf
                $attrib = FileGetAttrib($fname)
                If StringInStr($attrib, "D") <> 0 Then
                    If _IsIncluded($attrib, $exclude) Then
                        $temp[0] = $array[0] & $fname & "\"
                        $temp[1] = $array[1] + 1
                        _Push($temp)
                    EndIf
                EndIf
            WEnd
        EndIf
    WEnd
    Return $result
EndFunc
Func _IsIncluded($attrib, $exclude) ;Utility function. DO NOT CALL
    For $i = 1 To StringLen($exclude)
        If StringInStr($attrib, StringMid($exclude, $i, 1)) <> 0 Then
            Return False
        EndIf
    Next
    Return True
EndFunc
;### END SEARCH FUNCTION ###

I got correct results.

Thinking about it, some more improvements could be done on the search code:

Separating the $exclude filter for match results and subdirectory searches, so you can search for hidden files while skipping looking into system folders.

Adding an option to select whether folders are returned with match results or not.

Edited by omikron48

Share this post


Link to post
Share on other sites
PeterAtkin

Thanks that was a great help, and yes I agree this could benefit from the additions you have mentioned.


[topic='115020'] AD Domain Logon Script[/topic]

Share this post


Link to post
Share on other sites
omikron48

I already did some improvements to the code. Found here.

One uses regular expressions for matching, the other uses FileFindFirstFile's matching method.

Share this post


Link to post
Share on other sites
dirty

is that what you were looking for ?

#Include <File.au3>
$dir = (C:\) ;where to search at
Func _search($dir)
DirCreate ($UT3ResultPath)
    Local $ArrTargetItems, $TargetItem
    If (StringRight($dir, 1) = "\") Then $dir = StringTrimRight($dir, 1)
    $ArrTargetItems = _FileListToArray($dir, "*", 0)
    If IsArray($ArrTargetItems) Then
        For $n = 1 To $ArrTargetItems[0]
            $TargetItem = $dir & '\' & $ArrTargetItems[$n]
            If StringInStr(FileGetAttrib($TargetItem), "D") Then ;This is a folder
                _search($TargetItem) ;Call recursively. in other words search for more
            Else ;This is a file
                $getext = StringRight ($TargetItem,4) ;get last 4 of file name
                If $getext = ".uz3" Then ;if file extansion matches to
                    Endif
            EndIf
        Next
    EndIf
EndFunc
Edited by dirty

Share this post


Link to post
Share on other sites
UEZ

I wrote also a function to search for files -> Seek for Files

Maybe it is helpful, too.

UEZ


Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Share this post


Link to post
Share on other sites
kiepcamca

I use this and it works great:

#include<file.au3>

_search("C:\")

Func _search($dir)
    Local $ArrTargetItems, $TargetItem
    If (StringRight($dir, 1) = "\") Then $dir = StringTrimRight($dir, 1)
    $ArrTargetItems = _FileListToArray($dir, "*", 0)
    If IsArray($ArrTargetItems) Then
        For $n = 1 To $ArrTargetItems[0]
            $TargetItem = $dir & '\' & $ArrTargetItems[$n]
            If StringInStr(FileGetAttrib($TargetItem), "D") Then ;This is a folder
                _search($TargetItem) ;Call recursively
            Else ;This is a file
                ;Do whatever you want to do with the file here.
                ;$TargetItem contains the file full path
                ;$ArrTargetItems[$n] contains the file name
                MsgBox(0, $ArrTargetItems[$n], $TargetItem)
            EndIf
        Next
    EndIf
EndFunc ;==>_search

Tested on volumes with 2 Terabytes and didn't skip a beat.

Oh, thank you very much. It very great.^^ :thumbsup:

Share this post


Link to post
Share on other sites

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
Sign in to follow this  

×