【问题标题】:escaping "!" in a for-loop with delayed variable expansion转义“!”在具有延迟变量扩展的 for 循环中
【发布时间】:2019-11-08 23:40:43
【问题描述】:

我需要转义“!” (和其他特殊字符)在启用延迟变量扩展的 for 循环中

我尝试使用字符串替换手动转义循环变量^^!在循环变量中, %%a 但根本没有运气。在 for 阅读它们之后是否已经太晚了?如果是这样,我怎么能做到这一点?

下面是一个简短的函数。这里唯一相关的部分是 for 循环和 echo 语句。那就是每 X 行从文件中打印出整行,这些行是文件路径。它们(有时)包含诸如“!”之类的字符和其他麻烦的特殊字符。我只想在这里 echo 传递它而不解释它 - 但它最终会删除我的“!”字符。 对于我的使用,它们需要完全正确,否则它们将毫无用处,因为它们必须与我稍后使用它们的实际文件相关联。

setlocal EnableDelayedExpansion

:SplitList
if [%3] == [] goto :SplitListUsage
set inputfile=%~1
set outputfile=%~2
set splitnumber=%~3
set skipLines=0
set skipLines=%~4

if %skipLines% GTR 0 (
  set skip=skip=%skipLines%
) else (
  set skip=
)

@echo off > %outputfile%

set lineNumber=0
for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
    set /a modulo="!lineNumber! %% !splitnumber!"
    if "!modulo!" equ "0" (
        echo %%a >> !outputfile!
    )
    set /a lineNumber+=1
)
exit /B 0

【问题讨论】:

    标签: batch-file


    【解决方案1】:

    快速解决方案:

     if !modulo! equ 0 (
         setlocal DisableDelayedExpansion
         (echo %%a)>> "%outputfile%"
         endlocal
     )
    


    更好的解决方案:

    根据您的代码示例,无需为整个代码启用延迟扩展,
    事实上,您应该将其禁用,以免与可能包含! 的文件名或输入字符串混淆,并在必要时启用它:

    setlocal DisableDelayedExpansion
    
    REM Rest of the code...
    
    for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
        set /a "modulo=lineNumber %% splitnumber"
        setlocal EnableDelayedExpansion
        for %%m in (!modulo!) do (
            endlocal
            REM %%m is now have the value of modulo
            if %%m equ 0 (
                (echo %%a)>> "%outputfile%"
            )
        )
        set /a lineNumber+=1
    )
    


    旁注:

    您可能已经注意到,您的代码还有一些其他问题,其中一些问题已在上述解决方案中得到纠正。但为了不分散您对主要问题的注意力,我在这里分别介绍了它们。

    写入outputfile时,代码的性能还有提升的空间。

    这是重写的代码,涵盖了其余部分:

    @echo off
    setlocal DisableDelayedExpansion
    
    :SplitList
    if "%~3"=="" goto :SplitListUsage
    set "inputfile=%~1"
    set "outputfile=%~2"
    set "splitnumber=%~3"
    set "skipLines=0"
    set /a "skipLines=%~4 + 0" 2>nul
    
    if %skipLines% GTR 0 (
      set "skip=skip=%skipLines%"
    ) else (
      set "skip="
    )
    
    set "lineNumber=0"
    (
        for /f "usebackq tokens=* %skip% delims= " %%a in ("%inputfile%") do (
            set /a "modulo=lineNumber %% splitnumber"
            setlocal EnableDelayedExpansion
            for %%m in (!modulo!) do (
                endlocal
                REM %%m is now have the value of modulo
                if %%m equ 0 echo(%%a
            )
            set /a lineNumber+=1
        )
    )>"%outputfile%"
    
    • 您没有使用双引号保护变量赋值 " 例如set inputfile=%~1。如果现在裸批处理参数%~1 包含空格或特殊字符,例如&,您的批处理文件将失败,可能是由于语法错误而致命,或者在执行时数据不正确。推荐的语法是使用set "var=value",它不将引号分配给变量值,但提供对特殊字符的保护。它还可以保护分配免受意外尾随空格的影响。
    • %inputfile% 可能包含特殊字符或空格,因此在FOR /F 的IN 子句中使用时应通过双引号对其进行保护。在FOR /F 中对文件名进行双引号时,还必须使用usebackq 参数。
    • 使用SET /A 无需扩展变量值:set /a modulo="!lineNumber! %% !splitnumber!"。变量名可以直接使用,不管有没有延迟扩展,它都能正常工作。
    • 在FOR 循环中使用(echo %%a) >> "%outputfile%" 会导致严重的性能损失,特别是在进行大量迭代时,因为在每次迭代中,输出文件都会被打开、写入然后关闭。提高性能整个FOR循环可以重定向一次。任何新数据都将附加到已打开的文件中。
    • 奇怪的echo( 是为了防止空变量值,或者当变量值为/? 时。如果变量为空,使用echo %%a 可能会打印“ECHO 开启/关闭”消息,或者可能会打印回显使用帮助。
    • 在主要解决方案中,我使用(echo %%a)>> "%outputfile%" 而不是echo %%a >> "%outputfile%" 的原因是为了防止输出%%a 和>> 之间的额外空间。现在您知道使用echo( 的原因了,很容易理解更安全的替代方案:(echo(%%a)>> "%outputfile%"

    【讨论】:

    • 太棒了!这只是给了我很多解决这类问题的新工具。特别是使用 for 循环将变量带出延迟扩展本地的技巧是我以前从未想过的——但它工作得很好。我已经用它来解决另一个脚本中的不同问题 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多