0

I'm doing an assignment. Trying to get my batch file to read individual lines of an input file and figure out how many dots and commas it has. However, no matter what I do, the loop only runs once, so only the first line of the file is correctly processed, even though there are multiple lines. What am I missing?

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%

I have, of course, read other similar questions, but I can't figure out how, if at all, those apply to my problem, so sorry if this is a duplicate.

Oh, and if you have a better way to count individual characters, I'd be happy to hear it, but that's besides the point.

EDIT: here's CMD feed

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>

And here's the file I'm trying to read:

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

commas  and,
 dots 
Johnny Cache
  • 1,033
  • 7
  • 12
  • 1
    @echo on might actually show you the problem. –  Dec 26 '18 at 02:08
  • I have my echo on. It says nothing. Nothing I can decipher anyway. – Johnny Cache Dec 26 '18 at 02:12
  • You suggest that it sometimes works. Maybe explain that and show your research and debugging results. –  Dec 26 '18 at 02:15
  • It doesn't work "sometimes", it works for the first line only. Maybe I'm not very clear on that, let me fix that – Johnny Cache Dec 26 '18 at 02:16
  • Folks here will expect you to show the results of your research. Be very clear what you see when you run the script. –  Dec 26 '18 at 02:19
  • Here's the debug info. Still not seeing it. Maybe I don't know where to look? That's perfectly plausible. – Johnny Cache Dec 26 '18 at 02:28
  • 1
    You can't do a go-to wthin a FOR command. – Squashman Dec 26 '18 at 02:31
  • There we go! That's something. The question now, though, is what do I do then? I'm not sure I can count those without a goto. Also, just for the sake of curiosity: why can't I do a goto? What happens? – Johnny Cache Dec 26 '18 at 02:33
  • Have you trying putting subroutines outside the loop? – double-beep Dec 26 '18 at 05:24
  • Windows command processor does not support jump to a label inside body of __FOR__. Labels inside body of __FOR__ result in an undefined behavior of script execution. You could use a subroutine although that would dramatically increase the batch file execution time. See [Why is a GOTO loop much slower than a FOR loop and depends additionally on power supply?](https://stackoverflow.com/questions/53362979/) to see how one task was solved with four different solutions and how long it take to finish the task with each solution. – Mofi Dec 26 '18 at 08:46
  • Please consider accepting and upvoting my answer in case you find it helpful or leave feedback about it. See https://stackoverflow/help/someone-answers. – double-beep Feb 04 '19 at 19:16

2 Answers2

1

Windows command processor does not support jump to a label inside body of FOR. Labels inside body of FOR result in an undefined behavior of script execution.

@Mofi said in comments

So, you can't do it that way! If understood you want to make a batch file which will find how many dots and commas a file has. I have changed your file entirely and wrote a new one:

@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

Yes, it's true that it is quite big, but I couldn't simplify it further for a reason :)!

This file:

  • Sets wanted variables to 0.
  • Has a for loop which loops through laba2.txt textfile.
    • Only if line (%%A) contains dots (.) or commas (,) will be processed.
    • Finds size of line with another for loop.
    • Finds size of line without dots (.).
    • Does the same for commas (,).
    • In both cases we find difference of previous size of line and current size of line. We sum the result in the corresponding variable (%count_dots% or %count_commas%)
  • echo the variables
  • Remove temporary files.
double-beep
  • 5,031
  • 17
  • 33
  • 41
1

I recommend first reading Why is no string output with 'echo %var%' after using 'set var = text' on command line? and look on the line set b = !initial! which defines an environment variable with name (case-insensitive interpreted small letter B and space) with a space and the line read from text file as value.

Windows command processor does not support a jump to a label inside body of FOR.
Labels inside body of FOR result in an undefined behavior of script execution.

It is possible to use a subroutine although that dramatically increases the batch file execution time. See Why is a GOTO loop much slower than a FOR loop and depends additionally on power supply? to see how one task was solved with four different solutions and how long it take to finish the task with each solution.

Here is my solution for this task calling a subroutine for every line to count commas and dots in current line:

@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 with option /F reads text file laba2.txt line by line and ignores empty lines.

There are ignored by default by FOR also all lines starting with a semicolon because of eol=; is the default for end of line option. This behavior is changed with eol= which specifies no end of line character.

FOR splits up by default every line into substrings using normal space and horizontal tab as string delimiters and assigns just first space/tab delimited string to specified loop variable. This line splitting behavior is not wanted here, too. delims= defines an empty list of delimiters which disables the line splitting behavior.

Option usebackq is used because of the string in double quotes should be interpreted as file name of which lines should be read and not as string to process. In this special case the double quotes around file name laba2.txt would not be necessary which would make also usage of usebackq useless.

delims= and eol= can be specified in a double quoted options string, but only in this order. "usebackq eol= delims=" would not work because of this defines space as end of line character and an empty list of delimiters.

So FOR with the used options assigns every non-empty line read from specified file to specified loop variable I and runs the commands inside body of FOR loop.

The line is assigned next to environment variable with name Line. This is done while delayed environment variable expansion is disabled which is important on current line containing one or more exclamation marks. Otherwise Windows command processor would parse with enabled delayed expansion the command line set "Line=%%I" a second time after replacing %%I by current value of loop variable I and would interpret ! as beginning/end of an environment variable of which value is referenced delayed. This behavior is definitely not wanted here as it would result in modifying the line on assigning it to environment variable Line with in most cases removing parts of the line.

But delayed expansion is necessary to count the commas and dots in a line. It would be possible to enable delayed expansion after the command line set "Line=%%I" with setlocal EnableDelayedExpansion and use endlocal as last command line in body of FOR loop. But this is not possible here. For the reason read this answer with details about the commands SETLOCAL and ENDLOCAL.

Therefore a subroutine is called with call :Count to process the current line. The subroutine uses a FOR loop with option /L to compare every character of current line against , and . and which is exited on reaching end of line with a jump to a label below the FOR loop. This FOR loop speeds up line processing dramatically in comparison to a pure GOTO loop which would be also possible, but is much slower.

The command line after :ExitLoop is again something special. The command endlocal would restore previous environment which means the environment variables LineCountComma and LineCountDot do not exist anymore, at least not with the values set above and inside the FOR loop of the subroutine. So there are three commands specified on one command line which cmd.exe executes one after the other because of operator & whereby %LineCountComma% and %LineCountDot% are replaced by the current values of those two environment variables before executing this command line with the three commands. So endlocal deletes the environment variables LineCountComma and LineCountDot, but their values are already as strings on the command line and therefore the values pass the local environment barrier.

After reading every line from text file and processing every non-empty line in subroutine Count, the values of the two total counts are output before restoring the initial environment and exiting batch file execution with goto :EOF (if command extensions were enabled before starting this batch file as by Windows default).

I recommend reading Why does Windows have a limit on environment variables at all? regarding to value 8192 for maximum string length. In the code above is used set "Line=%%I". The maximum length of the line read from text file can be 8183 characters in this case because of command processor limit of 8191 bytes minus 4 bytes for variable name Line minus 1 byte for = minus 2 bytes for the two double quotes and most likely minus 1 byte for terminating null byte. So in real 8182 (character index of character 8183) could be used instead of 8192 in the loop as absolute maximum.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

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

See also:

PS: The usage of any other scripting language/interpreter than Windows command processor cmd.exe designed for execution of commands and applications would be definitely better for the task counting commas and dots in a text file.

Mofi
  • 46,139
  • 17
  • 80
  • 143