我在處理名稱包含以下內容的文件時遇到問題"^"
(插入符)。
我注意到的是,如果我在評估檔案名稱時使用雙引號,「插入符號」就會加倍。如果我不使用雙引號,檔案名稱中的「插入符」不會加倍(保留),但由於某些檔案名稱包含嵌入的空格,我必須使用雙引號來評估檔案名稱。
例如,我有一個包含一些文件的資料夾:
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 ^"
在第 6 階段偵測到 CALL 後,所有插入符都會加倍,並且以下內容將透過 CALL 機制:
echo Unquoted ^^ "Quoted ^^"
CALL 經歷第二階段 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 變數只在 FOR 語句的 DO 迴圈內具有作用域。當您退出迴圈時 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 for
或for /?
以獲得協助。相關部分大約在中間。請注意引文末尾的粗體字。
一些例子可能會有所幫助:
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k
將解析 myfile.txt 中的每一行,忽略以分號開頭的行,將每行的第二個和第三個標記傳遞到 for 正文,標記以逗號和/或空格分隔。請注意 for body 語句引用 %i 來取得第二個標記,%j 來取得第三個標記,%k 來取得第三個標記之後的所有剩餘標記。對於包含空格的檔名,需要用雙引號將檔名引起來。為了以這種方式使用雙引號,您還需要使用 usebackq 選項,否則雙引號將被解釋為定義要解析的文字字串。
%i 在 for 語句中明確聲明,%j 和 %k 透過 tokens= 選項隱式聲明。您可以透過 tokens= 行指定最多 26 個標記,前提是它不會導致嘗試聲明高於字母「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)