0

I've got a bunch of text files in a directory that have a block of text I want to extract between two strings into a new text file of a similar name. I've got the single file working but think I've come unstuck with looping through all .txt files. Maybe at the "goto" command? Here is the original, single file code I used: Batch File - Find two lines then copy everything between those lines

~Top Break
foobar
~ more data title
more foobar 
~Bottom Break
Garbage data

I have this code that works for a single file called FileNumber1.txt.

@echo off

    set "FIRSTLINE=~Top Break"
    set "LASTLINE=~Bottom Break"
    set "INFILE=FileNumber1.txt"
    setlocal EnableExtensions DisableDelayedExpansion
    set "FLAG="
    > "%INFILE%_MyData.txt" (
        rem findstr configured so that each line in a  file is given a "1:" number and colon.
        for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
            set "LINE=%%L"
            setlocal EnableDelayedExpansion
        rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
            set "LINE=!LINE:*:=!"
        rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
            if "!LINE!"=="%FIRSTLINE%" (
                endlocal
                set "FLAG=TRUE"
        rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
            ) else if "!LINE!"=="%LASTLINE%" (
                endlocal
                goto :CONTINUE
            ) else if defined FLAG (
                echo(#!LINE!
                endlocal
            ) else (
                endlocal
            )
        )
    )
    :CONTINUE
    endlocal
    
    
    
   

NewFile1_MyData.txt Output:

foobar
~ more data title
more foobar 

I've tried to wrap this in another "FOR" loop that looks for all txt files in the same directory. This is my code that isn't working.

@echo off

set "FIRSTLINE=~Top Break"
set "LASTLINE=~Bottom Break"

for /F %%f in (*.txt) do (
set "INFILE=%%f"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%INFILE%_OldHeader.txt" (
    rem findstr configured so that each line in a  file is given a "1:" number and colon.
    for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
        set "LINE=%%L"
        setlocal EnableDelayedExpansion
    rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
        set "LINE=!LINE:*:=!"
    rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
        if "!LINE!"=="%FIRSTLINE%" (
            endlocal
            set "FLAG=TRUE"
    rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
        ) else if "!LINE!"=="%LASTLINE%" (
            endlocal
            goto :CONTINUE
        ) else if defined FLAG (
            echo(#!LINE!
            endlocal
        ) else (
            endlocal
        )
    )
)

endlocal


:CONTINUE
))

The Command window gets to the "for /F" statement and exits.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Nick_Jo
  • 101
  • 9
  • Reading about For loops and Goto's it seems that Goto's kill For loops. I think that I'll try a second For loop instead. – Nick_Jo Jul 27 '21 at 05:40
  • 2
    Yes, `goto` breaks all loops it is situated in, even nested ones. Moreover, [delayed expansion](https://ss64.com/nt/delayedexpansion.html) is required for all environment variables that are set and expanded within the same block of code, like a loop. I would place the functional code from the other post inside of a sub-routine and call it from the body of another `for` loop in the main section of the script… – aschipfl Jul 27 '21 at 07:01
  • 1
    `for %%f in (*.txt) do call "working code for one file.bat" "%%f" and change in "working code for one file.bat" to `set "INFILE=%~1". Or integrate your working batchfile and `call :label "%%f"` – Stephan Jul 27 '21 at 07:45
  • @ Stephan Thanks! Your first suggested process worked well. I'll end up using @Aacini's though as it's faster. You've given me a good suggestion going forward. – Nick_Jo Jul 28 '21 at 03:25

1 Answers1

1

Mmm... I would change the method to extract the lines for a simpler one based on lines to skip at beginning of file and number of lines to extract. After that, I would use a for to process all files and call a subroutine to extract the lines:

@echo off
setlocal EnableDelayedExpansion

set "FirstLine=~Top Break"
set "LastLine=~Bottom Break"

rem Process all text files in this folder
for %%f in (*.txt) do (

   rem Search for First line and Number of lines
   set "FirstNum="
   for /F "delims=:" %%n in ('findstr /C:"%FirstLine%" /C:"%LastLine%" /N "%%f"') do (
      if not defined FirstNum (
         set "FirstNum=%%n"
      ) else (
         set /A "LastNum=%%n-FirstNum-1"
      )
   )

   rem Copy the lines
   call :CopyLines >"%%~Nf_MyData.out" "%%f", !FirstNum!, !LastNum!  

)
ren *.out *.txt
goto :EOF


:CopyLines File, Skip, Num
set "Num=%3"
for /F "usebackq skip=%2 delims=" %%a in (%1) do (
   setlocal DisableDelayedExpansion
   echo %%a
   endlocal
   set /A Num-=1
   if !Num! equ 0 exit /B
)
exit /B
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Thanks @Aacini. Worked well, and it seems a lot quicker than my original script for a single file. I'm going to dissect your CopyLines subroutine - I don't understand the line `set /A Num=-1` – Nick_Jo Jul 28 '21 at 02:42
  • 1
    @Nick_Jo, the line is `set /A Num-=1`; the operator `-=` (not `=-` as you wrote) subtracts the value to the right from the variable to the left and reassigns the result, which is equivalent to `set /A Num=Num-1`; type `set /?` into a Command Prompt window and read the help; also refer to [Arithmetic expressions (SET /a)](https://ss64.com/nt/set.html#expressions)… – aschipfl Jul 28 '21 at 10:54
  • I like this approach, but it might be problematic with exclamation marks and caret symbols, because delayed expansion is enabled during expansion of `%1`; a simple fix was to assign a variable in the main loop (`set "Line=%%f"`), to pass the variable name to the sub-routine (`call :CopyLines >"%%~Nf_MyData.out" Line !FirstNum! !LastNum!`) and to replace `%1` in the sub-routine by `!%1!`… – aschipfl Jul 28 '21 at 11:03