如何使用 ! 处理路径名在 for /F 循环中?

How to process pathnames with ! within an for /F loop?

在一个复杂的批处理文件中,我想读入带有路径的文件,除其他外,将它们一个接一个地读入一个变量中,用空格分隔。

到目前为止,这与以下代码配合使用效果很好 - 但前提是路径不包含感叹号。

即使使用 setlocal 命令(enabledelayedexpansion / disabledelayedexpansion)我也没有成功处理感叹号。

这里有没有人对这个问题有好的想法?

以下示例批处理在当前目录中创建一个文本文件,然后在 for /F 循环中读取它。 最后,文本文件中的所有三个路径都应位于变量 %Output% 中。但是带有感叹号。

@echo off

setlocal enabledelayedexpansion

echo This is an example^^! > "textfile.txt"
echo This is a second example^^! >> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle >> "textfile.txt"

for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
    set "Record=%%a"
    set "Output=!Output!!Record! - "
  )
)

echo %Output%
echo !Output!

endlocal

输出是这样的:

This is an example  - This is a second example  - And this line have an  exclamation mark in the middle

但应该是这样的:

This is an example!  - This is a second example!  - And this line have an ! exclamation mark in the middle

似乎是 delayedexpansion 模式引发了这个问题。

@ECHO OFF
setlocal enabledelayedexpansion

echo This is an example^^^! > "textfile.txt"
echo This is a second example^^^! >> "textfile.txt"
echo And this line have an ^^^! exclamation mark in the middle >> "textfile.txt"
TYPE "textfile.txt"
SETLOCAL disabledelayedexpansion
for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
    set "Record=%%a"
    CALL set "Output2=%%Output2%%%%record%% - "
    CALL set "Output=%%Output%%%%a - "
    SET out
  )
)
endlocal&SET "output=%output%"

echo %Output%
echo !Output!
SET out

我毫不怀疑,关闭 delayedexpansion 后,% 也会出现同样的问题。我想只是特殊字符。

注意endlocal&SET "output=%output%"setdelayedexpansion模式下执行。

皮尤。我终于让它工作了。 它通过使用第二个文本文件的变通方法工作。 不漂亮,不高效,但它有效并且足以满足我的目的。 @Magoo,感谢您的 post.

这是我的解决方案:

@echo off
setlocal enableextensions enabledelayedexpansion

echo This is an example^^!> "textfile.txt"
echo This is a second example^^!>> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle>> "textfile.txt"
echo.
echo Content of the textfile:
type "textfile.txt"

set output=

del "textfile2.txt" 1> nul 2>&1
setlocal disabledelayedexpansion

for /f "usebackq tokens=* delims=" %%a IN ("textfile.txt") do (
  rem Write each line without a newline character into a new text file
  echo|set /p "dummy=%%a, ">>"textfile2.txt"
)

endlocal

rem Loading the content of the new text file into the variable
set /p output=<"textfile2.txt"
del "textfile2.txt" 1> nul 2>&1

echo.
echo --------------------------------------------
echo Content of the variable:

set out

endlocal

输出如下所示:

Content of the textfile:
This is an example!
This is a second example!
And this line have an ! exclamation mark in the middle

--------------------------------------------
Content of the variable:
output=This is an example!, This is a second example!, And this line have an ! exclamation mark in the middle,

建议不要对处理文件和目录、文本文件中的行、批处理文件本身未定义的字符串或从程序或命令行的执行中捕获的输出使用延迟变量扩展。如果由于某些原因需要在 FOR 循环中使用延迟变量扩展,应该首先分配 file/directory 名称、行或要处理的字符串在禁用延迟扩展的情况下添加到环境变量,然后在 FOR 循环中临时启用延迟扩展。

这是一个批处理文件演示,只需在命令提示符 window 中 运行 或双击批处理文件即可。它在临时文件目录中创建了几个文件用于演示,但在退出前将它们全部删除。

@echo off
setlocal EnableExtensions DisableDelayedExpansion

echo This is an example!> "%TEMP%\TextFile.tmp"
echo This is a second example!>> "%TEMP%\TextFile.tmp"
echo And this line has an exclamation mark ! in the middle.>> "%TEMP%\TextFile.tmp"

set "Output="
(for /F usebackq^ delims^=^ eol^= %%I in ("%TEMP%\TextFile.tmp") do set "Line=%%I" & call :ConcatenateLines) & goto ContinueDemo
:ConcatenateLines
set "Output=%Output% - %Line%" & goto :EOF

:ContinueDemo
cls
echo/
echo All lines concatenated are:
echo/
echo %Output:~3%
set "Output="
del "%TEMP%\TextFile.tmp"

echo File with name ".Linux hidden file!">"%TEMP%\.Linux hidden file!"
echo File with name "A simple test!">"%TEMP%\A simple test!"
echo File with name "  100%% Development & 'Test' (!).tmp">"%TEMP%\  100%% Development & 'Test(!)'.tmp"
echo/
echo Files with ! are:
echo/
for /F "eol=| tokens=* delims=" %%I in ('dir "%TEMP%\*!*" /A-D /B /ON 2^>nul') do (
    set "NameFile=%%I"
    set "FileName=%%~nI"
    set "FileExtension=%%~xI"
    set "FullName=%TEMP%\%%I"
    setlocal EnableDelayedExpansion
    if defined FileName (
        if defined FileExtension (
            echo File with ext. !FileExtension:~1!: !NameFile!
        ) else (
            echo Extensionless file: !NameFile!
        )
    ) else echo Extensionless file: !NameFile!
    del "!FullName!"
    endlocal
)
endlocal
echo/
@setlocal EnableExtensions EnableDelayedExpansion & for /F "tokens=1,2" %%G in ("!CMDCMDLINE!") do @endlocal & if /I "%%~nG" == "cmd" if /I "%%~H" == "/c" set /P "=Press any key to exit the demo . . . "<nul & pause >nul

这个批处理文件的输出是:

All lines concatenated are:

This is an example! - This is a second example! - And this line has an exclamation mark ! in the middle.

Files with ! are:

File with ext. tmp:   100% Development & 'Test(!)'.tmp
Extensionless file: .Linux hidden file!
Extensionless file: A simple test!

带有连接行的文本文件示例使用了从 FOR 循环中调用的子例程来处理文本文件中的行。此处使用的语法是通过使子例程尽可能接近 FOR 命令行来获得最佳性能。如果 FOR 循环必须处理数百甚至数千个项目,那么这一点很重要。

示例处理文件名在将当前处理的文件的所有部分分配给环境变量后启用和禁用 FOR 循环内的延迟扩展。在处理数千个文件之前减少环境变量列表可能很有用,以便使用此方法获得更好的性能。

中显示了另一种方法,使用命令 CALL 获取命令行,并在第二次解析的循环内(重新)定义引用环境变量。我过去也经常使用该方法,但不要再使用了,因为它不是 fail-safe 并且效率不高。 call set 导致在当前目录和环境变量 PATH 的所有目录中按 cmd.exe 搜索名称为 set 且文件扩展名为环境变量 [=16] 的文件=].因此,它会在 FOR 循环的每次迭代中导致在后台进行大量文件系统访问,如果偶然出现文件 set.exeset.batset.cmd,等 cmd.exe 在某处发现,批处理文件不再按预期工作,因为 运行 宁可执行文件或调用批处理文件而不是(重新)定义环境变量。

我写的以下答案也可能有帮助:


  • 它详细解释了如何处理文本文件的所有行。

  • 它详细解释了命令 SETLOCALENDLOCAL 的作用。

  • 这是一个批处理文件示例,旨在处理任何 Windows 计算机上具有任何有效文件名的视频文件,非常高效、安全且有完整解释。

嗯,主要技巧是仅在实际需要时启用延迟扩展,否则禁用它。由于您在循环内的单个变量中累积多个字符串,因此变得有点困难,因为您应该在扩展 for meta-variables 期间禁用延迟扩展(如 %%a) , 但在加入字符串时启用,导致循环内的 setlocalendlocal 语句。这些命令的主要目的是环境本地化,因此任何变量更改都会在 endlocal 之后丢失,因此需要一种将值转移到 endlocal 之外的方法,该方法包含在以下代码中:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem /* At this point delayed expansion is disabled, so there is no need to
rem    escape exclamation marks; moreover a redirected block prevents
rem    superfluous file close/reopen operations, and there is no more
rem    trailing space written to the file (as in your original approach): */
> "textfile.txt" (
    echo This is an example!
    echo This is a second example!
    echo And this line have an ! exclamation mark in the middle
)

rem // Let us initialise the output variable:
set "Output= - "
rem // Using `usebackq` only makes sense when you want to quote a file path:
for /F "usebackq tokens=* delims=" %%a in ("textfile.txt") do (
    rem // Remember that delayed expansion is still disabled at this point:
    set "Record=%%a"
    rem // For concatenation we need delayed expansion to be enabled:
    setlocal EnableDelayedExpansion
    set "Output=!Output!!Record! - "
    rem /* We need to terminate the environment localisation of `setlocal`
    rem    inside of the loop, but we would lose any changes in `Output`;
    rem    therefore let us (mis-)use `for /F`, which is iterated once: */
    for /F "delims=" %%b in ("!Output!") do endlocal & set "Output=%%b"
    rem /* An often used method to transfer a variable beyond `endlocal` is
    rem    the line `endlocal & set "Output=%Output%`, but this only works
    rem    outside of a parenthesised block because of percent expansion. */
)

rem /* Echo out text with delayed expansion enabled is the only safe way;
rem    surrounding separators ` - ` are going to be removed; since `Output`
rem    was initialised with something non-empty, we do not even need to skip
rem    sub-string expansion for the problematic case of an empty string: */
setlocal EnableDelayedExpansion
echo(!Output:~3,-3!
endlocal

endlocal
exit /B