简体   繁体   中英

Windows Batch script stops reading a file after a record has been written via IF in DO loop

Sometimes I think my relationship with the Windows command line is weighted in favour of Stockholms syndrome over anthing else. I'm hoping someone else in this world of punishing stuff has an answer for me!

Here's the background and the problem statement: I have a quite large script that does stuff with a for loop that steps through a file searching for a record number, and changing the record while writing the records around in a nice sequential batch way, ie records are written up to the target record; a changed record is written; the remaining records are written. I needed to add a new action verb to the existing set, which requires an additional IF clause. It suddenly stopped writing out remaining records after the found record had been changed. Couldn't find the answer.

So I started stripping away code until I reached this residual script. The IF clause in the FOR/ in / DO loop ... Leave it in, the script stalls after the written record as per the first sample below; leave it out, the FOR loop happily does it's thing, as per the second sample output. It gets stranger. Adding in script gives error messages, sometimes running through to completion, sometimes not ~ even a simple echo will give a parsing error. Placement in the script also seems to matter, which made debugging an absolute nightmare.

Clearly there is something that is triggering the command processor to quit the loop. SO I would pose the following two questions to the community:

  1. What is causing the FOR /in /DO structure to stop processing records

  2. What gives with the echo statements giving parsing errors? (echoing a variable name with a script line number, or just the number, for instance, is very low impact. )

Thanks. Code follows, any text file can be used as the second parameter, the first is a line number to action.

echo off
SETLOCAL enabledelayedexpansion

SET _filein=%~2
SET _line=%1
set _cnt=0
:: _filein = any file, _line = the line to be inspected, _cnt = loop counter

:: in the loop: _record = line value, _msg= display message if line is found
FOR /f "tokens=1 delims=" %%i IN ('type %_filein%') DO (
  SET _record=%%i
  SET /a _cnt+=1
  IF [!_cnt!]==[%_line%] (
    echo the line %_line% is !_record!
    set _msg=the line is !_record! at position !_cnt!
    goto sayrecord
  )
  echo not the line:
  :sayrecord
  echo !_cnt!- !_record!
)
:Xit
echo:
IF [%_msg%]==[] (
  echo: left out
) ELSE (
  echo: I'm in to see %_msg%
)

ENDLOCAL
exit /b

Test runs:

1. With the IF statement: (note parsing error statement)

H>_a 3 _files.txt

H>echo off
not the line:
1- notes.bat
not the line:
2- notesqa.bat
the line 3 is bulkfiledelete.bat
3- bulkfiledelete.bat

line was unexpected at this time.

H>

Without the IF:

H>_a 3 _files.txt

H>echo off
not the line:
1- notes.bat
not the line:
2- notesqa.bat
not the line:
3- bulkfiledelete.bat
not the line:
4- CheckAdminRights.bat
not the line:
 :   :   :
not the line:
47- Reset connection 3 - renew.lnk
not the line:
48- ============

 left out

H>

OK, so I have a code block that works thanks to @jwdonaahue for the initial clue; and @SomethingDark for the problem statement.

The :label is the problem here, although it works in many other production scripts it clearly is problematic. Instead of a script that drops to the bottom of the loop for processing, putting in an IF clause for each use case is what is needed.

@echo off
SETLOCAL enabledelayedexpansion

SET _filein=%~2
SET _line=%1
set _cnt=0
:: _filein = any file, _line = the line to be inspected, _cnt = loop counter

:: in the loop: _record = line value, _msg= display message if line is found
FOR /f "tokens=1 delims=" %%i IN ('type %_filein%') DO (
  SET _record=%%i
  SET /a _cnt+=1
  IF [!_cnt!]==[%_line%] (
    echo the line %_line% is !_record!
    set "_msg=the line is !_record! at position !_cnt!"
    echo not the line:
    CALL :sayrecord
  )
  IF [!_cnt!] neq [%_line%] CALL :sayrecord
)
:Xit
echo:
IF [%_msg%]==[] (
  echo: left out
) ELSE (
  echo: I'm in to see %_msg%
)

ENDLOCAL
exit /b

:sayrecord
echo !_cnt!- !_record!
exit /b

ie to solve my problem,

  1. add an IF clause for each action verb;
  2. end with an `IF' clause which wraps the records that must be written unchanged

I'll also go through all the scripts and do a maint on them!

Given that your stated intent was to replace the content of %2 on line %1 , you don't need delayed expansion or to set all of those variables:

@For /F "Tokens=1*Delims=]" %%I In ('Type "%~2"^|"%__AppDir__%find.exe" /V /N ""')Do @If "%%I"=="[%~1%" (Echo Inserted line content)Else Echo=%%J
@Pause

You would modify the content after Echo and before ) to the replacement line content, and the last line is added just to ensure that you can read the output.


[Edit /]
If you wanted to ask the end user for confirmation of the change before doing so, you could expand your code to incorporate that:

 @For /F "Tokens=1*Delims=]" %%I In ('Type "%~2"^|"%__AppDir__%find.exe" /V /N ""')Do @If "%%I"=="[%~1" ("%__AppDir__%choice.exe" /M "Replace %%J with new content"&If ErrorLevel 2 (Echo=%%J)Else Echo Inserted line content)Else Echo=%%J @Pause

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM