이름에 다음이 포함된 파일을 처리하는 데 문제가 있습니다."^"
(탈자 부호).
내가 주목한 것은 파일 이름을 평가할 때 큰따옴표를 사용하면 "캐럿"이 두 배가 된다는 것입니다. 큰따옴표를 사용하지 않으면 파일 이름의 "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
하나의 Q와 A에서 두 가지 모호한 Windows 배치 동작을 발견해 주셔서 감사합니다!
배치 또는 명령줄에서 CALL을 통해 홀수 개의 인용 캐럿을 문자열 리터럴로 전달하는 것은 불가능합니다. 설명은 6단계에서 찾을 수 있습니다.Windows 명령 해석기(CMD.EXE)는 스크립트를 어떻게 구문 분석합니까?.
다음은 문제의 예입니다. 스크립트에 다음 명령이 포함되어 있다고 가정합니다.
call echo Unquoted ^^ "Quoted ^"
파서의 2단계 이후 인용되지 않은 부분은 이스케이프 동작의 일부로 캐럿을 사용합니다. 인용된 부분은 그대로 둡니다. 이제 명령은 다음과 같습니다.
call echo Unquoted ^ "Quoted ^"
6단계에서 CALL이 감지되면 모든 캐럿이 두 배가 되고 다음이 CALL 메커니즘을 통해 전달됩니다.
echo Unquoted ^^ "Quoted ^^"
CALL은 2단계 2)를 거치며 결과는 다음과 같습니다.
echo Unquoted ^ "Quoted ^^"
다음과 같은 최종 출력을 생성합니다.
Unquoted ^ "Quoted ^^"
FOR 루프를 사용한 예제는 FOR 변수 확장이 2단계 후에 발생하기 때문에 초기 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)나는 문자열 내의 특수 문자가 인용되는지 여부에 대해 걱정할 필요가 없도록 지연된 확장을 사용하는 것을 선호합니다. SET 문 내에서 변수 이름 앞에 여는 따옴표가 나타나기 때문에 var의 값은 따옴표로 묶이지 않습니다.
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
삼)에서 했던 것처럼 FOR 변수 "터널링"을 사용하십시오.너의 답. 예전에 이 기술을 사용해 본 적이 있지만 난독화되어 있어서 마음에 들지 않습니다. 코드를 작성한 후 유지관리하려는 사람은 아마도 무슨 일이 일어나고 있는지 이해하지 못할 것입니다.
FOR 변수는 FOR 문의 DO 루프 내에서만 범위를 갖습니다. 루프에서 호출하면 범위가 종료됩니다. 그러나 발견한 바와 같이 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의 각 줄을 구문 분석하고 세미콜론으로 시작하는 줄을 무시하고 각 줄의 두 번째 및 세 번째 토큰을 쉼표 및/또는 공백으로 구분된 토큰을 사용하여 본문에 전달합니다. for body 문은 두 번째 토큰을 얻으려면 %i를 참조하고, 세 번째 토큰을 얻으려면 %j를, 세 번째 토큰 이후 남은 모든 토큰을 얻으려면 %k를 참조하세요. 공백이 포함된 파일 이름의 경우 파일 이름을 큰따옴표로 묶어야 합니다. 이런 방식으로 큰따옴표를 사용하려면 usebackq 옵션도 사용해야 합니다. 그렇지 않으면 큰따옴표가 구문 분석할 리터럴 문자열을 정의하는 것으로 해석됩니다.
%i는 for 문에서 명시적으로 선언되고 %j 및 %k는 tokens= 옵션을 통해 암시적으로 선언됩니다. 문자 'z' 또는 'Z'보다 큰 변수를 선언하려고 시도하지 않는 경우 tokens= 행을 통해 최대 26개의 토큰을 지정할 수 있습니다. 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)