Переменная ведет себя странно в этом простом пакетном скрипте

У меня есть этот скрипт, который проверяет, используются ли каталоги, пытаясь переименовать каталог, а также, кроме того, проверяет, содержит ли он какие-либо известные временные файлы. он работает хорошо, за исключением случаев, когда он встречает каталог с указанными временными файлами.

:: 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"
)

каким-то образом после этой строки dir /s *.!qb *.part *.dash* *.tmp >nul 2>nul && cd .. && goto skip сценарий останавливается, и я получаю вывод done with "%r", где каждый второй каталог показывает имя каталога для %%r, поэтому ожидается done with "directory name".


50
1

Ответ:

Решено

В этом пакетном файле нет меток, которые не работают внутри командного блока, начинающегося с ( и заканчивающегося соответствующим ), поскольку весь командный блок сначала анализируется перед выполнением любой командной строки, включая командную строку FOR. См.: Как интерпретатор команд Windows (CMD.EXE) анализирует сценарии?

@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

Имя пакетного файла с полным путем, но без расширения файла, уже определено в начале пакетного файла. См. причину: По какой причине путь к пакетному файлу, указанный в %~dp0, иногда меняется при смене каталога?

Рассматриваемый пакетный файл работает с текущим рабочим каталогом, который определяется вне пакетного файла процессом, запускающим cmd.exe для обработки пакетного файла. Таким образом, это может быть любой каталог. Пакетный файл в этом ответе использует %CD%\* для поиска нескрытых каталогов в текущем рабочем каталоге, что отличается от простого *. Найденный каталог имеет полный путь, назначенный переменной цикла G, а не только имя каталога без пути. Здесь это важно, поскольку pushd "%SystemDrive%\" помещает путь к текущему рабочему каталогу в стек и затем меняет текущий рабочий каталог на корневой каталог системного диска. Полный путь к начальному текущему каталогу с именем текущего каталога процесса необходим внутри цикла FOR для других командных строк.

%CD% обычно заменяется строкой пути, не заканчивающейся обратной косой чертой. Исключением является то, что текущий каталог является корневым каталогом диска, и в этом случае %CD% заменяется буквой диска, двоеточием и обратной косой чертой. Однако C:\\* здесь не проблема благодаря автоматическим исправлениям файлового ввода-вывода Windows, как описано в документации Microsoft об Именовании файлов, путей и пространств имен.

Внутри цикла FOR текущий каталог изменяется на корневой каталог системного диска с помещением текущего рабочего каталога в стек. Я действительно не понимаю причину cd /d "%target%" с неизвестным определением переменной среды target. Также можно использовать pushd "%target%", если target определен строкой пути к папке, которая еще не заключена в ".

Затем пытается переименовать найденный нескрытый подкаталог, добавляя (ctest) к имени каталога. Если это не удается из-за того, что каталог или любой подкаталог этого каталога или любой файл в этом каталоге или в его подкаталогах используется любым запущенным процессом или из-за отсутствия разрешений, и поэтому код выхода равен 1, большинство других строк внутри Цикл FOR игнорируется из-за первого условия IF.

При успешном переименовании каталога каталог немедленно переименовывается обратно без повторной проверки, сработало ли это. Это должно работать всегда, иначе результатом будет повреждение структуры каталогов, вызванное пакетным файлом.

Текущий каталог снова изменяется с помощью команды PUSHD now на текущий обрабатываемый подкаталог исходного текущего рабочего каталога.

Выполняется команда DIR для поиска файлов (не каталогов), включая скрытые файлы, которые по умолчанию игнорируются, если не используется опция /A, соответствующая одному из шаблонов подстановочных знаков во всем дереве каталогов текущего каталога.

Второе условие IF истинно только в том случае, если не удалось найти файл, соответствующий одному из шаблонов подстановочных знаков. В этом случае выполняется внутренняя команда FOR, которая рекурсивно вызывает все командные файлы с именем .cmd в совершенно другом каталоге с полным путем к командному файлу и с именем командного файла.

Я предполагаю, что опция /R используется только для получения имен пакетных файлов с полным путем, присвоенным переменной цикла H, а указанный каталог вообще не содержит подкаталогов. Если мое предположение верно, то в качестве внутренней командной строки FOR можно также использовать:

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

Команда POPD выполняется для второго PUSHD, чтобы извлечь из стека путь к последнему загруженному каталогу и временно снова сделать этот каталог текущим каталогом, который является корневым каталогом системного диска.

Последние две командные строки внутри цикла FOR извлекают исходный путь к текущему каталогу из стека и снова делают этот каталог текущим рабочим каталогом перед выводом информационного сообщения, и FOR ищет следующий нескрытый каталог в исходном текущем рабочем каталоге.

Чтобы понять, какие команды используются и как они работают, откройте окно командной строки , выполните там следующие команды и внимательно прочитайте отображаемые страницы справки для каждой команды.

  • call /? … для объяснения %~dpn0 … диск + путь + имя аргумента 0 … имя пакетного файла с полным путем, но без расширения файла.
  • cmd /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • popd /?
  • pushd /?
  • rem /?
  • ren /?
  • set /? … для объяснения динамической переменной CD
  • setlocal /?