windowsvariablesbatch-filebatch-processingwindows-11

Variable behaving oddly in this simple Batch script


I've got this script that checks if directories are being used by trying to rename the dir, and additionally, checking if it contains some known temp files. it works well except when it encounters a dir with said temp files.

:: open each top dir for inspection and processing
for /d %%r in (*) do (
    rem security
    cd /d "%target%"
    ren "%%r" "%%r(ctest)" || goto skip
    ren "%%r(ctest)" "%%r" || goto skip
    cd /d "%%r" || goto skip
    dir /s *.!qb *.part *.dash* *.tmp >nul 2>nul && cd .. && goto skip
    if defined verbose echo importing "%%~nxr"
    rem call any scripts in the subdir with this script's own name
    for /r "%~dp0%~n0\" %%s in (*.cmd) do call "%%s"
    :skip
    rem keep this (label can't be last)
    echo done with "%%r"
)

somehow, after that line dir /s *.!qb *.part *.dash* *.tmp >nul 2>nul && cd .. && goto skip the script stops, and I get output done with "%r", where every other dir shows the dir name for %%r, so done with "directory name" is expected.


Solution

  • This batch file is without labels which do not work inside a command block beginning with ( and ending with matching ) as the entire command block is parsed first before executing any command line including the FOR command line. See: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "BatchName=%~dpn0"
    for /D %%G in ("%CD%\*") do (
        pushd "%SystemDrive%\"
        ren "%%G" "%%~nxG(ctest)"
        if not errorlevel 1 (
            ren "%%G(ctest)" "%%~nxG"
            pushd "%%G"
            dir /A-D /S *.!qb *.part *.dash* *.tmp >nul 2>nul
            if errorlevel 1 (
                if defined verbose echo importing "%%~nxG"
                rem call any scripts in the subdir with this script's own name
                for /R "%BatchName%\" %%H in (*.cmd) do call "%%H"
            )
            popd
        )
        popd
        echo done with "%%~nxG"
    )
    endlocal
    

    The batch file name with full path but without file extension is already determined at the beginning of the batch file. For the reason see: What is the reason for batch file path referenced with %~dp0 sometimes changes on changing directory?

    The batch file in question works with current working directory which is defined outside of the batch file by the process starting cmd.exe for processing the batch file. It can be therefore any directory. The batch file in this answer uses %CD%\* to search for non-hidden directories in the current working directory which makes a difference to just *. The found directory is with full path assigned to the loop variable G instead of just the directory name without path. That is important here because of pushd "%SystemDrive%\" pushes the current working directory path on stack and changes next the current working directory to root directory of the system drive. The full path to initial current directory with the name of the currently process directory is needed inside the FOR loop for the other command lines.

    %CD% expands usually to a path string not ending with a backslash. The exception is that the current directory is the root directory of a drive in which case %CD% expands to drive letter, colon and a backslash. However, C:\\* is no problem here thanks to the automatic corrections of the Windows file I/O as explained by the Microsoft documentation about Naming Files, Paths, and Namespaces.

    Inside the FOR loop the current directory is modified to the root directory of the system drive with pushing the current working directory on stack. I really don't understand the reason for cd /d "%target%" with unknown definition of the environment variable target. There can be used also pushd "%target%" if target is defined with a folder path string which is not already enclosed in ".

    Then is tried a rename of the found non-hidden subdirectory by appending (ctest) to the directory name. If that fails because of the directory or any subdirectory of this directory or any file in this directory or in its subdirectories is in use by any running process or because of missing permissions and so the exit code is 1, most of the other lines inside the FOR loop are ignored because of the first IF condition.

    On a successful directory rename the directory is immediately renamed back without checking once again if that worked. It should always work as otherwise the result is a directory structure corruption caused by the batch file.

    The current directory is changed once again using the command PUSHD now to the currently processed subdirectory of the initial current working directory.

    There is executed the command DIR to search for files (not directories) including hidden files which are ignored by default on not using option /A matching one of the wildcard patterns in entire directory tree of the current directory.

    The second IF condition is true only if no file could be found matching one of the wildcard patterns. In this case the inner FOR command is executed which calls recursively all batch files with name .cmd in a completely different directory with full path of the batch file and with name of the batch file.

    I suppose the option /R is used only for getting the batch file names with full path assigned to the loop variable H and the referenced directory does not contain subdirectories at all. If my supposition is right, there could be used as inner FOR command line also:

    for %%H in ("%BatchName%\*.cmd") do call "%%H"
    

    The command POPD is executed for the second PUSHD to pop the last pushed directory path from stack and make this directory temporarily again the current directory which is the root directory of the system drive.

    The last two command lines inside the FOR loop pop the initial current directory path from stack and makes this directory again the current working directory before the information message is output and the next non-hidden directory is searched by FOR in the initial current working directory.

    To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.