Search code examples
batch-filefor-looprecursionpiping

Recursive directory search loop


I'm pretty inexperienced with batch scripting, but I need to do the following:

-Recursively Loop through a path input into subdirectories

-For files that match the pattern cls.py and ins.py, execute a command

-Pipe all output to a text file.

For starters, I created a batch file to just try and display the matched file names. Upon execution however, it just echos the if statements a bunch of times.

set /p searchdir=Enter Path:
FOR /r %%f in (%%searchdir%%) do (
IF %%~nxf =='ins.py' echo %%~p1
IF %%~nxf =='cls.py' echo %%~p1
)
pause

What am I doing wrong? I'm sure it's stupid, and constructive is criticism welcome to aid my learning.


Solution

  • set /p path=Enter Path:
    

    PATH is an important system environment variable that you generally don't want to over-write in a script file unless you really know what you are doing. You may sometimes want to modify it, but that is not the case here.

    FOR /r %%f in (%%path%%) do (
    

    Normally you'd follow the /r with the root path and you don't need the double percents around the path variable. Something like:

    for /r "%_rootPath%" %%f in (*.py) do @echo %%f
    

    But even that's not quite what you want. Your if statements are really messed up:

    IF %%~nxf =='ins.py' echo %%~p1
    IF %%~nxf =='cls.py' echo %%~p1
    

    Whatever %%~nxf expands to can't possibly match the right side of the comparison due to the mismatched quotes. Anyway, the whole thing is inefficient because your looping over all of the files per directory looking for matches.

    One way to do this is to iterate over the files your looking for and then let the dir command find all the directories they are in:

    @setlocal ENABLEEXTENSIONS
    @rem @set prompt=$G
    
    @rem Space delimited list of scripts to run, listed in execution order.
    @set _scriptsToRun=ins.py cls.py
    
    @set _root=
    @set /p _root=Enter Path:
    @if not defined _root @set _root=%CD%
    @if not exist %_root% @goto :BadPath
    @for %%A in (%_scriptsToRun%) do @call :FindAndExecuteThem "%_root%" "%%A"
    @exit /b 0
    
    :BadPath
    @set _PATH_NOT_FOUND=3
    @echo Path not found: %_root%
    @exit /b %_PATH_NOT_FOUND%
    
    :FindAndExecuteThem
    @rem @echo %%1==%1
    @rem @echo %%2==%2
    @for /f %%A in ('dir /b /s "%~1\%2"') do @call :RunIt "%%A"
    @rem @for /f %%A in ('where /r %~1 "%2"') do @call :RunIt "%%A"
    @exit /b 0
    
    :RunIt
    rem @call python.exe %1
    rem @call %1
    rem @start python.exe %1
    

    Note that the above code avoids using multi-line code blocks. Makes life easier when debugging your work. Also, you'll probably need a path to the correct version of the python executable. You can run start script.py on some systems, but that's assuming the extension has been mapped to the python.exe file. But you can just call python.exe and skip an extra process startup.

    Note that I adopted a convention that script local variables should always have a leading underscore. This way, when the script dies during development due to some error I've made or because of misuse, it's a simple mater to find and remove all the variables starting with an underscore. I also use set prompt=$g within my scripts during development and usually take it out later. This ties-in with the use of the @ symbol prefix on every command, which allows me to acquire pin-hole diagnostics by removing a select few of them and not having to wade through an entire scripts worth of spew with echo turned off. Also, avoid echo on/off in scripts makes them slightly easier to compose with other scripts and there is less risk of modifying the user's environment.