在 Windows 中處理名稱中包含“^”(插入符)的文件

在 Windows 中處理名稱中包含“^”(插入符)的文件

我在處理名稱包含以下內容的文件時遇到問題"^"(插入符)。

我注意到的是,如果我在評估檔案名稱時使用雙引號,「插入符號」就會加倍。如果我不使用雙引號,檔案名稱中的「插入符」不會加倍(保留),但由於某些檔案名稱包含嵌入的空格,我必須使用雙引號來評估檔案名稱。

例如,我有一個包含一些文件的資料夾:

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 forfor /?以獲得協助。相關部分大約在中間。請注意引文末尾的粗體字。

一些例子可能會有所幫助:

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)

相關內容