在 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 退出循环时,作用域结束。但是,正如您所发现的,如果被调用的例程有自己的 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 在 (myfile.txt) 中执行@echo %i %j %k

将解析 myfile.txt 中的每一行,忽略以分号开头的行,将每行中的第 2 和第 3 个标记传递给 for 主体,标记由逗号和/或空格分隔。请注意,for 主体语句引用 %i 以获取第 2 个标记,%j 以获取第 3 个标记,%k 以获取第 3 个标记之后的所有剩余标记。对于包含空格的文件名,您需要用双引号将文件名括起来。为了以这种方式使用双引号,您还需要使用 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)

相关内容