【问题标题】:Batch file FOR loop won't loop批处理文件 FOR 循环不会循环
【发布时间】:2018-12-26 01:56:28
【问题描述】:

我正在做作业。试图让我的批处理文件读取输入文件的各个行并找出它有多少个点和逗号。但是,无论我做什么,循环只运行一次,因此即使有多行,也只有文件的第一行被正确处理。我错过了什么?

REM @echo off
setlocal enabledelayedexpansion
set count=-2
for /F "delims=" %%I in (laba2.txt) DO (
set initial=%%I
set b = !initial!
:againdot
set oldb=!b!
set "b=!b:*.=!"
set /a count+=1
echo "b = !b!, oldb = !oldb!"
if not !oldb! == !b! goto :againdot
set b=!initial!
:againcomma
set oldb=!b!
set "b=!b:*,=!"
set /a count+=1
echo "b = !b!, oldb = !oldb!"
if not !oldb! == !b! goto :againcomma
)
echo %count%

当然,我读过其他类似的问题,但我不知道这些问题如何适用于我的问题,如果有重复,请见谅。

哦,如果你有更好的方法来计算单个字符,我会很高兴听到它,但这不是重点。

编辑:这里是 CMD 提要

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>test.bat

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>REM @echo off

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>setlocal enabledelayedexpansion

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set count=-2

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>for /F "delims=" %I in (laba2.txt) DO (
set initial=%I
 set b = !initial!
 set oldb=!b!
 set "b=!b:*.=!"
 set /a count+=1
 echo "b = !b!, oldb = !oldb!"
 if not !oldb! == !b! goto :againdot
 set b=!initial!
 set oldb=!b!
 set "b=!b:*,=!"
 set /a count+=1
 echo "b = !b!, oldb = !oldb!"
 if not !oldb! == !b! goto :againcomma
)

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>(
set initial=this,is .a random
 set b = !initial!
 set oldb=!b!
 set "b=!b:*.=!"
 set /a count+=1
 echo "b = !b!, oldb = !oldb!"
 if not !oldb! == !b! goto :againdot
 set b=!initial!
 set oldb=!b!
 set "b=!b:*,=!"
 set /a count+=1
 echo "b = !b!, oldb = !oldb!"
 if not !oldb! == !b! goto :againcomma
)
"b = *.=, oldb = "

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set oldb=!b!

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set "b=!b:*.=!"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set /a count+=1

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>echo "b = !b!, oldb = !oldb!"
"b = =, oldb = *.="

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>if not !oldb! == !b! goto :againdot

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set oldb=!b!

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set "b=!b:*.=!"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set /a count+=1

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>echo "b = !b!, oldb = !oldb!"
"b = =, oldb = ="

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>if not !oldb! == !b! goto :againdot

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set b=!initial!

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set oldb=!b!

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set "b=!b:*,=!"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set /a count+=1

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>echo "b = !b!, oldb = !oldb!"
"b = is .a random, oldb = this,is .a random"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>if not !oldb! == !b! goto :againcomma

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set oldb=!b!

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set "b=!b:*,=!"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>set /a count+=1

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>echo "b = !b!, oldb = !oldb!"
"b = is .a random, oldb = is .a random"

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>if not !oldb! == !b! goto :againcomma

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>echo 3
3

C:\Users\fiksi\Desktop\Учёбка\ОС 3с\Лаба 2>

这是我要阅读的文件:

this,is .a random
..,arran,gement of.

commas  and,
 dots 

【问题讨论】:

  • @echo on 实际上可能会向您显示问题。
  • 我有我的回声。它什么也没说。无论如何我都无法破译。
  • 您建议它有时会起作用。也许解释一下并展示你的研究和调试结果。
  • 它“有时”不起作用,它仅适用于第一行。也许我不是很清楚,让我解决这个问题
  • 您无法使用 FOR 命令进行跳转。

标签: windows file batch-file for-loop


【解决方案1】:

我建议首先阅读Why is no string output with 'echo %var%' after using 'set var = text' on command line? 并查看set b = !initial! 行,它定义了一个名为(不区分大小写的小写字母B 和空格)的环境变量,其中包含一个空格,从文本文件中读取的行为价值。

Windows 命令处理器不支持跳转到 FOR 正文内的标签。
FOR 主体内的标签会导致脚本执行的未定义行为。

可以使用子例程,尽管这会显着增加批处理文件的执行时间。请参阅Why is a GOTO loop much slower than a FOR loop and depends additionally on power supply?,了解如何使用四种不同的解决方案解决一项任务,以及使用每种解决方案完成任务需要多长时间。

这是我对这个任务的解决方案,它为每一行调用一个子例程来计算当前行中的逗号和点:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "TotalCountComma=0"
set "TotalCountDot=0"

for /F "usebackq delims= eol=" %%I in ("laba2.txt") do (
    set "Line=%%I"
    call :Count
)

if %TotalCountComma% == 1 (set "CommaS=") else set "CommaS=s"
if %TotalCountDot% == 1 (set "DotS=") else set "DotS=s"
echo The file contains %TotalCountComma% comma%CommaS% and %TotalCountDot% dot%DotS%.
endlocal
goto :EOF

:Count
setlocal EnableDelayedExpansion
set "LineCountComma=0"
set "LineCountDot=0"
set "LineLength=1"
for /L %%J in (0,1,8192) do (
    if "!Line:~%%J,1!" == "" goto ExitLoop
    if "!Line:~%%J,1!" == "," set /A LineCountComma+=1
    if "!Line:~%%J,1!" == "." set /A LineCountDot+=1
)
:ExitLoop
endlocal & set /A "TotalCountComma+=%LineCountComma%" & set /A "TotalCountDot+=%LineCountDot%"
goto :EOF

FOR 带有选项 /F 逐行读取文本文件 laba2.txt 并忽略空行。

默认情况下 FOR 会忽略所有以分号开头的行,因为 eol=; 是行尾选项的默认值。这个行为被 eol= 改变了,它没有指定行尾字符。

FOR 默认情况下将每一行拆分为子字符串,使用普通空格和水平制表符作为字符串分隔符,并将第一个空格/制表符分隔的字符串分配给指定的循环变量。这里也不需要这种线分割行为。 delims= 定义了一个空的分隔符列表,它禁用了行拆分行为。

使用选项usebackq是因为双引号中的字符串应该被解释为应该读取哪些行的文件名,而不是要处理的字符串。在这种特殊情况下,不需要在文件名 laba2.txt 周围加上双引号,这也会使 usebackq 的使用变得无用。

delims= eol= 可以在双引号选项字符串中指定,但只能按此顺序。 "usebackq eol= delims=" 不起作用,因为它将空格定义为行尾字符和空的分隔符列表。

所以 FOR 使用 used 选项将从指定文件读取的每个非空行分配给指定的循环变量 I 并在 FOR 循环的主体内运行命令。

该行被分配在名称为Line 的环境变量旁边。这是在禁用延迟的环境变量扩展时完成的,这对包含一个或多个感叹号的当前行很重要。否则,Windows 命令处理器将在将%%I 替换为循环变量I 的当前值之后第二次解析启用的delayed expansion 命令行set "Line=%%I",并将! 解释为环境变量的开始/结束值被延迟引用。此处绝对不希望出现这种行为,因为它会导致修改将其分配给环境变量 Line 的行,在大多数情况下会删除部分行。

但是延迟扩展对于计算一行中的逗号和点是必要的。可以在命令行set "Line=%%I"setlocal EnableDelayedExpansion 之后启用延迟扩展,并使用endlocal 作为FOR 循环体中的最后一个命令行。但这在这里是不可能的。为此,请阅读 this answer 了解有关命令 SETLOCALENDLOCAL 的详细信息。

因此,使用call :Count 调用子程序来处理当前行。该子例程使用带有选项/LFOR 循环将当前行的每个字符与,. 进行比较,并在到达行尾时退出并跳转到下方的标签FOR 循环。与纯 GOTO 循环相比,此 FOR 循环显着加快了行处理速度,后者也是可能的,但速度要慢得多。

:ExitLoop 之后的命令行又是特别的东西。命令endlocal 将恢复以前的环境,这意味着环境变量LineCountCommaLineCountDot 不再存在,至少在上面设置的值和子例程的FOR 循环内不存在。因此,在一个命令行上指定了三个命令,cmd.exe 一个接一个地执行,因为运算符& 从而%LineCountComma%%LineCountDot% 在执行此命令行之前被这两个环境变量的当前值替换三个命令。所以endlocal 删除了环境变量LineCountCommaLineCountDot,但是它们的值已经是命令行中的字符串,因此这些值通过了本地环境屏障。

在从文本文件中读取每一行并处理子程序Count中的每一个非空行之后,在恢复初始环境并使用goto :EOF退出批处理文件执行之前,输出两个总计数的值(如果命令扩展默认情况下,在启动此批处理文件之前已启用)。

我建议阅读 Why does Windows have a limit on environment variables at all? 关于值 8192 以获得最大字符串长度。在上面的代码中使用了set "Line=%%I"。在这种情况下,从文本文件中读取的行的最大长度可以是 8183 字符,因为命令处理器限制为 8191 字节 minus 4 变量名称的字节 Line 减号 1 字节表示= 减号 2 字节表示两个双引号 最有可能 减号 @987654379 @byte 用于终止空字节。所以在真正的8182(字符8183的字符索引)可以在循环中使用而不是8192作为绝对最大值。

要了解所使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • call /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • set /?
  • setlocal /?

另见:

PS:除 Windows 命令处理器 cmd.exe 外,使用任何其他脚本语言/解释器来执行命令和应用程序对于在文本文件中计算逗号和点的任务肯定会更好。

【讨论】:

    【解决方案2】:

    Windows 命令处理器不支持跳转到 FOR 正文内的标签。 FOR 主体内的标签会导致脚本执行的未定义行为。

    @Mofi 在 cmets 中说

    所以,你不能那样做!如果理解你想制作一个批处理文件,它将找出一个文件有多少个点和逗号。我已经完全改变了你的文件并写了一个新的:

    @echo off
    
    set /a "count_dots=0"
    set /a "count_commas=0"
    
    powershell.exe -NoP -C "(Get-Content laba2.txt) -Replace '!',''|Set-Content $env:TEMP\laba2_tmp.txt"
    setlocal EnableDelayedExpansion
    for /f "delims= eol=" %%A IN (%TEMP%\laba2_tmp.txt) do (
        set line=%%A
    
        rem Find size of variable:
        (echo !line!)>%TEMP%\temp.txt
        for %%B IN (%TEMP%\temp.txt) do set size=%%~zB
    
        rem Check if the line has got commas:
        if not "!line:,=!" == "!line!" (
            rem Find size of variable *without* commas:
            (echo !line:,=!)>%TEMP%\temp.txt
            for %%B IN (%TEMP%\temp.txt) do set sizecommas=%%~zB
    
            rem Find difference of previous and new size of variable:
            if !size! NEQ !sizecommas! (set /a "diffcommas=size-sizecommas" & set /a "count_commas+=diffcommas")
    
    
        rem Check if the line has got dots:
        if not "!line:.=!" == "!line!" (
            rem Find size of variable *without* dots:
            (echo !line:.=!)>%TEMP%\temp.txt
            for %%B IN (%TEMP%\temp.txt) do set sizedots=%%~zB
    
            rem Find difference of previous and new size of variable:
            if !size! NEQ !sizedots! (set /a "diffdots=size-sizedots" & set /a "count_dots+=diffdots")
            )
        )
    )
    
    rem Delete temporary files:
    del %TEMP%\temp.txt %TEMP%\laba2_tmp.txt
    
    if %count_dots% == 1 (echo Found %count_dots% dot in specified document.) else (echo Found %count_dots% dots in specified document.)
    if %count_commas% == 1 (echo Found %count_commas% comma in specified document.) else (echo Found %count_commas% commas in specified document.)
    
    pause
    

    是的,它确实很大,但由于某种原因我无法进一步简化它:)!

    这个文件:

    • 将想要的变量设置为0
    • 有一个循环通过laba2.txt 文本文件的for 循环。
      • 仅当行 (%%A) 包含点 (.) 或逗号 (,) 时才会被处理。
      • 用另一个 for 循环查找行的大小。
      • 查找没有点的线的大小 (.)。
      • 对逗号 (,) 也是如此。
      • 在这两种情况下,我们都发现之前的行大小和当前行的大小存在差异。我们将结果相加到相应的变量中(%count_dots%%count_commas%
    • echo 变量
    • 删除临时文件。

    【讨论】:

      猜你喜欢
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 2013-10-07
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多