У меня возникли проблемы с обработкой файлов, имена которых содержат"^"
(Курочка).
Я заметил, что если я использую двойные кавычки при оценке имен файлов, "Caret's" удваиваются. Если я не использую двойные кавычки, "Caret's" в именах файлов НЕ удваиваются (сохраняются), но поскольку некоторые имена файлов содержат встроенные пробелы, мне приходится оценивать имена файлов С двойными кавычками.
В качестве примера у меня есть папка, содержащая несколько файлов:
G:\Test-folder\Abcxyz 1.txt
G:\Test-folder\Abcxyz2.txt
G:\Test-folder\Abcxyz3.txt
G:\Test-folder\Abc^xyz 1.txt
G:\Test-folder\Abc^xyz2.txt
G:\Test-folder\Abc^xyz3^.txt
У меня есть пакетный скрипт, который собирает имена файлов, затем считывает их и обрабатывает каждый файл.
@echo off
rem collect the filenames
dir /s /b "G:\Test-folder\ab*" > "G:\Test-folder\list.txt"
rem Note: here I have an opportunity to inspect and modify the filenames as necessary, but I have not found any modifications that solve this problem.
rem process each file
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work "%%~f"
@echo.
@echo Back: f1="%f1%"
@echo.
@echo.
@echo Running again, with "setlocal enabledelayedexpansion".
@echo.
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work2 "%%~f"
@echo.
@echo Back: f2="%f2%"
@echo.
goto :EOF
:work
rem :work
set "f1=%~1"
if exist "%f1%" goto :dostuff
@echo.
@echo File "%f1%" not found.
@echo %f1%
@echo "%~1"
@echo %~1
@echo.
goto :EOF
:dostuff
rem do some stuff :dostuff
@echo File "%f1%" found.
goto :EOF
:work2
rem :work2
setlocal enabledelayedexpansion
set "f2=%~1"
if exist "!f2!" goto :dostuff2
@echo.
@echo File "!f2!" not found.
@echo !f2!
@echo "%~1"
@echo %~1
@echo.
endlocal
goto :EOF
:dostuff2
rem do some stuff :dostuff2
@echo File "!f2!" found.
endlocal
goto :EOF
Запустив этот скрипт, я получаю следующий вывод:
File "G:\Test-folder\Abcxyz 1.txt" found.
File "G:\Test-folder\Abcxyz2.txt" found.
File "G:\Test-folder\Abcxyz3.txt" found.
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^xyz3^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
Back: f1="G:\Test-folder\Abc^^xyz3^^.txt"
Снова бегу, с setlocal enabledelayedexpansion
.
File "G:\Test-folder\Abcxyz 1.txt" found.
File "G:\Test-folder\Abcxyz2.txt" found.
File "G:\Test-folder\Abcxyz3.txt" found.
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^^xyz3^^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
Back: f2=""
Итак, в любом случае, с использованием или без использования"enabledelayedexpansion"
, Я не могу обрабатывать файлы с именами, содержащими"^"
(Курочка).
Есть идеи, как это сделать или что я делаю не так?
решение1
Поигравшись с этим некоторое время, я придумал следующее рабочее решение:
@echo off
rem collect the filenames
dir /s /b "G:\Test-folder\ab*" >"G:\Test-folder\list.txt"
rem process each file
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work "%%~f"
@echo.
rem Note: I still could not make this work with "setlocal enabledelayedexpansion".
goto :EOF
:work
rem :work
set "f1=%~1"
if exist "%f1%" goto :dostuff
@echo.
@echo File "%f1%" not found.
@echo %f1%
@echo "%~1"
@echo %~1
@echo.
rem Notice that the "action" of this (next) for-loop is: [set "f1=%%~f"]
rem which uses the "for-variable" from the "outer" for-loop: "%%f"
rem instead of the "for-variable" from the "this" for-loop: "%%g"
@for /f "usebackq delims=" %%g in (`echo "dummy"`) do set "f1=%%~f"
if exist "%f1%" goto :dostuff
@echo File "%f1%" not found.
@echo %f1%
@echo.
goto :EOF
:dostuff
rem do some stuff :dostuff
@echo File "%f1%" found.
for %%g in ("%f1%") do echo name:"%%~ng" extn:"%%~xg" file-size:"%%~zg"
@echo.
goto :EOF
Результат выполнения этого скрипта:
File "G:\Test-folder\Abcxyz 1.txt" found.
name:"Abcxyz 1" extn:".txt" file-size:"14"
File "G:\Test-folder\Abcxyz2.txt" found.
name:"Abcxyz2" extn:".txt" file-size:"13"
File "G:\Test-folder\Abcxyz3.txt" found.
name:"Abcxyz3" extn:".txt" file-size:"13"
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^xyz 1.txt" found.
name:"Abc^xyz 1" extn:".txt" file-size:"15"
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^xyz2.txt" found.
name:"Abc^xyz2" extn:".txt" file-size:"14"
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^xyz3^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
File "G:\Test-folder\Abc^xyz3^.txt" found.
name:"Abc^xyz3^" extn:".txt" file-size:"15"
Я случайно «наткнулся» на это работающее решение, использующее метод, который может быть недокументированным поведением вложенных циклов for.
Я пытался использовать "sed" для изменения"^^"
в строке в кавычках к одному"^"
, так:
@for /f "usebackq delims=" %%g in (`echo "%f1%"^|sed -r "s/(\x5e)\1/\1/g"`) do set "f1=%%~g"
Я по ошибке набрал это:
@for /f "usebackq delims=" %%g in (`echo "%f1%"^|sed -r "s/(\x5e)\1/\1/g"`) do set "f1=%%~f"
Я не был удивлен (сначала), когда это сработало, потому что я думал, что "sed" работает так, как и ожидалось. Затем я заметил, что использовал неправильную переменную for:set "f1=%%~f"
вместо:set "f1=%%~g"
, что было удивительно.
Я изменил его, чтобы использовать правильную переменную:set "f1=%%~g"
, но оказалось, что это не сработало.
Я пробовал разные версии, в том числе:
@for /f "usebackq delims=" %%g in (`echo "%f1%"`) do set "f1=%%~g"
ничего из этого не сработало.
Итак, это, похоже, работает только если это "неправильно" используется с использованием неправильной переменной for. Хотя это кажется полезным в данном случае, мне трудно поверить, что это будет работать в долгосрочной перспективе.
Мне было бы очень интересно услышать мнение других, является ли это на самом деле «документированным» (ожидаемым) поведением или нет.
решение2
Честь и хвала вам за то, что вы обнаружили два непонятных поведения пакетной обработки Windows в одном вопросе и ответе!
Невозможно передать нечетное количество кавычек в качестве строкового литерала через CALL ни в пакете, ни в командной строке. Объяснение можно найти в фазе 6) наКак интерпретатор команд Windows (CMD.EXE) анализирует скрипты?.
Вот пример проблемы. Предположим, скрипт содержит следующую команду:
call echo Unquoted ^^ "Quoted ^"
После фазы 2 парсера часть без кавычек потребляет каретку как часть поведения выхода. Часть в кавычках остается в покое. Теперь команда выглядит так:
call echo Unquoted ^ "Quoted ^"
При обнаружении CALL на этапе 6 все каретки удваиваются, и следующее передается через механизм CALL:
echo Unquoted ^^ "Quoted ^^"
CALL проходит вторую фазу 2), в результате чего:
echo Unquoted ^ "Quoted ^^"
Получаем следующий конечный результат:
Unquoted ^ "Quoted ^^"
Ваш пример с циклом FOR обходит начальную фазу 2, поскольку расширение переменной FOR происходит после фазы 2.
Решение — не передавайте строковые литералы в кавычках, содержащие символ каретки, через CALL.Используйте альтернативную стратегию. Есть несколько вариантов. Я перечислил несколько ниже.
1а)Не используйте CALL вообще. Вы можете использовать скобки после DO для создания произвольно сложного кода. Это, безусловно, моя любимая стратегия, потому что CALL по своей природе медленный. Единственное, что вы не можете сделать, это использовать GOTO внутри цикла, так как это немедленно прервет обработку цикла. Если вам нужно манипулировать переменными внутри цикла, то вам нужно будет включить и использовать отложенное расширение.
setlocal enableDelayedExpansion
for ....%%A in (...) do (
set "var=%%A"
echo the value of var=!var!
... whatever
)
1б)Если переменная FOR может содержать !, то необходимо включать и выключать отложенное расширение внутри цикла, чтобы предотвратить повреждение.
for ... %%A in (...) do (
setlocal enableDelayedExpansion
set "var=%%A"
echo the value of var=!var!
... whatever
endlocal
)
2а)Если вы действительно хотите использовать CALL, то не передавайте значение как строковый литерал. Вместо этого сохраните значение в переменной окружения. Обратите внимание, что значение var заключено в кавычки для защиты от специальных символов.
for ... %%A in (...) do (
set var="%%~A"
call :work
)
exit /b
:work
echo var=%var%
... etc.
exit /b
2б)Я предпочитаю использовать отложенное расширение, чтобы не беспокоиться о том, заключены ли специальные символы в строку в кавычки. Обратите внимание, что значение var не заключено в кавычки, поскольку открывающая кавычка появляется перед именем переменной в операторе SET.
for ... %%A in (...) do (
set "var=%%~A"
call :work
)
exit /b
:work
setlocal enableDelayedExpansion
echo var=!var!
... etc.
exit /b
2в)Вместо того, чтобы писать подпрограмму, которая знает, как работать только с одной переменной, вы можете передать имя переменной в качестве аргумента. Это требует отложенного расширения.
for ... %%A in (...) do (
set "var=%%~A"
call :work var
)
exit /b
:work
setlocal enableDelayedExpansion
echo %1=!%1!
... etc.
exit /b
3)Используйте переменную FOR «туннелирование», как вы делали вВаш ответ. Я использовал эту технику в прошлом, но она мне не нравится, потому что она запутывает. Тот, кто попытается поддерживать код после того, как он был написан, вероятно, не поймет, что происходит.
Переменные FOR имеют область действия только в пределах цикла DO оператора FOR. Когда вы CALL выходите из цикла, область действия заканчивается. Но, как вы обнаружили, если CALLed-подпрограмма имеет свой собственный оператор FOR, старые переменные FOR «волшебным образом» снова появляются.
for ... %%A in (...) do call :work
exit /b
:work
echo The A variable is no longer in scope: %%A
for %%x in (x) do echo The A variable is back: %%A
Объяснение в том, что переменные FOR являются глобальными, но доступны только внутри цикла DO. Это зашифровано объясняется во встроенной системе HELP. Введите help for
или , for /?
чтобы получить справку. Соответствующий раздел находится примерно на полпути вниз. Обратите внимание на выделенное жирным шрифтом слово ближе к концу цитаты.
Вот несколько примеров, которые могут быть полезны:
ДЛЯ /F "eol=; токены=2,3* разделители=, " %i в (myfile.txt) сделать @echo %i %j %k
будет анализировать каждую строку в myfile.txt, игнорируя строки, начинающиеся с точки с запятой, передавая 2-й и 3-й токен из каждой строки в тело for, при этом токены разделяются запятыми и/или пробелами. Обратите внимание, что операторы тела for ссылаются на %i для получения 2-го токена, %j для получения 3-го токена и %k для получения всех оставшихся токенов после 3-го. Для имен файлов, содержащих пробелы, необходимо заключать имена файлов в двойные кавычки. Чтобы использовать двойные кавычки таким образом, необходимо также использовать опцию usebackq, в противном случае двойные кавычки будут интерпретироваться как определение литеральной строки для анализа.
%i явно объявлен в операторе for, а %j и %k неявно объявлены через опцию tokens=. Вы можете указать до 26 токенов через строку tokens=, при условии, что это не приведет к попытке объявить переменную выше буквы 'z' или 'Z'. Помните, переменные FOR являются однобуквенными, чувствительными к регистру,Глобальный, и вы не можете иметь более 52 активных одновременно.
Это самая официальная документация, которую я когда-либо видел для этого поведения. Очень загадочно и не очень полезно. На самом деле, большая часть информации в этом последнем абзаце простонеправильный! Видетьhttps://stackoverflow.com/a/8520993/1012053для получения информации о максимальном количестве доступных переменных FOR и о допустимых символах для переменных FOR.
решение3
Я абсолютно не эксперт, и поэтому не могу указать, что не так в вашей многословной программе. Но я запустил этот пакет и нашел некоторый предполагаемый результат:
@ECHO OFF
DIR /b /s >list.txt
SETLOCAL enabledelayedexpansion
FOR /f "delims=" %%x IN (list.txt) DO IF EXIST "%%x" (@ECHO %%x found) else (@ECHO %%x not found)