1

У меня проблемы с обработкой файлов с именами, которые содержат "^" (Карат).

Что я замечаю, так это то, что если я использую двойные кавычки при оценке имен файлов, «Карат» удваивается. Если я не использую двойные кавычки, «Караты» в именах файлов НЕ удваиваются (сохраняются), но, поскольку некоторые из имен файлов содержат встроенные пробелы, я должен оценить имена файлов с помощью двойных кавычек.

В качестве примера у меня есть папка, которая содержит несколько файлов:

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"


Running again, with "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", я не могу обрабатывать файлы с именами, которые содержат "^" (Карат).

Любые идеи о том, как это сделать, или что я делаю не так?

3 ответа3

2

Поработав с этим некоторое время, я придумал это рабочее решение:

@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. Хотя это кажется полезным в этом случае, мне трудно поверить, что это будет работать в долгосрочной перспективе.

Мне было бы очень интересно услышать от других, является ли это "задокументированным" (ожидаемым) поведением или нет.

1

Спасибо вам за то, что вы обнаружили два неясных поведения Windows в одном Q и A!

Невозможно передать нечетное количество заключенных в кавычки в виде строкового литерала через CALL в пакетном режиме или в командной строке. Объяснение можно найти в Фазе 6) в разделе Как интерпретатор сценариев команд Windows (CMD.EXE) анализирует сценарии? ,

Вот пример проблемы. Предположим, что скрипт содержит следующую команду:

call echo Unquoted ^^ "Quoted ^"

После фазы 2 синтаксического анализатора часть без кавычек потребляет символ каретки как часть поведения escape. Цитируемая часть оставлена одна. Команда теперь выглядит так:

call echo Unquoted ^ "Quoted ^"

При обнаружении CALL на этапе 6 все каретки удваиваются, и через механизм CALL проходит следующее:

echo Unquoted ^^ "Quoted ^^"

ЗВОНОК проходит второй этап 2), в результате чего:

echo Unquoted ^ "Quoted ^^"

Производим следующий конечный результат:

Unquoted ^ "Quoted ^^"

Ваш пример с циклом FOR обходит начальную фазу 2, потому что расширение переменной FOR происходит после фазы 2.


Решение - не передавайте строковые литералы, заключенные в кавычки, через CALL. Используйте альтернативную стратегию. Есть несколько вариантов. Я перечислил несколько ниже.

1a) Не используйте CALL вообще. Вы можете использовать круглые скобки после DO для создания произвольно сложного кода. Это, безусловно, моя любимая стратегия, потому что CALL по своей сути медленный. Единственное, что вы не можете сделать, это использовать GOTO внутри цикла, так как это немедленно прекратит обработку цикла. Если вам нужно манипулировать переменными внутри цикла, вам нужно будет включить и использовать отложенное расширение.

setlocal enableDelayedExpansion
for ....%%A  in (...) do (
  set "var=%%A"
  echo the value of var=!var!
  ... whatever
)


1b) Если переменная FOR может содержать!, То вы должны включить или выключить отложенное расширение в цикле, чтобы предотвратить повреждение.

for ... %%A in (...) do (
  setlocal enableDelayedExpansion
  set "var=%%A"
  echo the value of var=!var!
  ... whatever
  endlocal
)


2a) Если вы действительно хотите использовать CALL, не передавайте значение как строковый литерал. Вместо этого сохраните значение в переменной окружения. Обратите внимание, что значение var указано для защиты от специальных символов.

for ... %%A in (...) do (
  set var="%%~A"
  call :work
)
exit /b

:work
echo var=%var%
... etc.
exit /b


2b) Я предпочитаю использовать отложенное расширение, чтобы не беспокоиться о том, заключаются ли в кавычки специальные символы в строке. Обратите внимание, что значение var не заключено в кавычки, поскольку открывающая кавычка появляется перед именем переменной в инструкции SET.

for ... %%A in (...) do (
  set "var=%%~A"
  call :work
)
exit /b

:work
setlocal enableDelayedExpansion
echo var=!var!
... etc.
exit /b


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

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. Когда вы вызываете из цикла, область действия заканчивается. Но, как вы обнаружили, если подпрограмма 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 /? чтобы получить помощь. Соответствующий раздел находится примерно на полпути вниз. Обратите внимание на жирное слово в конце цитаты.

Некоторые примеры могут помочь:

FOR /F "eol =; токены = 2,3 * delims =,"% i in (myfile.txt) do @echo% i% j% k

будет анализировать каждую строку в myfile.txt, игнорируя строки, начинающиеся с точки с запятой, передавая 2-й и 3-й токен от каждой строки в тело for, с токенами, разделенными запятыми и / или пробелами. Обратите внимание, что операторы body ссылаются на% 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.

0

Я абсолютно не эксперт, и поэтому не могу указать, что не так в вашей многословной программе. Но я запустил эту партию и нашел желаемый результат:

@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)

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .