windowsfor-loopbatch-filedelimiterfindstr

Loop through all lines in a text file, while preserving empty lines AND all characters


I need to loop through every line in a text file, and still have the empty lines. The common solution I could find is as follows:

for /f "tokens=1* delims=:" %%i in ('findstr /n "^" "%textfile%"') do ( call :IsLabel _return "%%b" )

This will prefix every line with the line number and colon character, example:

21:R 30 35 89jtj3 G)(#G_ 23ty9ug9dg

The problem is that, if the first character is a colon, (:), it will get dropped due to delims=: in the for loop.

So 30::mylabel becomes just mylabel instead of :mylabel.

Is there a way to make findstr use another character than colon perhaps?

-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-EDIT-

Since, no matter what, there will be extra characters to remove, I think that the best course of action is to dump the entire file into an array. Then remove the characters from that array.

So what is needed is a "FileToArray" function that takes a filename and array name and populates the array with the content of that file.

My first attempt, based on @Mofi's answer is as follows

::Usage Call :SimpleFileToArray OutputArray Filename
:SimpleFileToArray
set "_FTA_Output=%~1"
set "_FTA_ubound=1"
setlocal enabledelayedexpansion
set _FTA_localscope=true
for /f delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /n "^" "%~2"') do (
    set _FTA_buffer=%%I
    set %_FTA_Output%[!_FTA_ubound!]=!_FTA_buffer:*:=!
    set /a "_FTA_ubound+=1"
    )
for /F "delims=" %%a in ('set %_FTA_Output% 2^>nul') do endlocal & set %%a
if defined _FTA_localscope endlocal
GoTo :EOF

(execution time, 4 second for 1600 lines on slow computer)

This version almost works, however, as @Mofi points out, is that doing this you lose all the ! characters on the line

set _FTA_buffer=%%I

@Mofi then suggests to perform the removal of the line numbers in a setlocal inside the for loop instead. In the example, this works because the data is echo'd out directly and doesn't need to be kept in a variable outside of setlocal

So, I tried this variant with a setlocal / endlocal inside the for loop

::Usage Call :SimpleFileToArray OutputArray Filename
:SimpleFileToArray
set "_FTA_Output=%~1"
set "_FTA_ubound=1"
for /f delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /n "^" "%~2"') do (
    set _FTA_buffer=%%I
    setlocal enabledelayedexpansion
    set _FTA_buffer=!_FTA_buffer:*:=!
    echo set %_FTA_Output%[!_FTA_ubound!]=!_FTA_buffer!
    set %_FTA_Output%[!_FTA_ubound!]=!_FTA_buffer!
    set /a "_FTA_ubound+=1"
    set _FTA_ubound=!_FTA_ubound!
    endlocal & set /a "_FTA_ubound=%_FTA_ubound%" & set %_FTA_Output%[%_FTA_ubound%]=%_FTA_buffer%
    )
GoTo :EOF

(same execution time)

Unfortunately, this does not set the outputarray values.

If I echo(%_FTA_buffer% , I can see the values get passed substituted properly but can't get them out of that endlocal

I tried other permutations

::Usage Call :SimpleFileToArray OutputArray Filename
:SimpleFileToArray
set "_FTA_Output=%~1"
set "_FTA_ubound=1"
setlocal enabledelayedexpansion
set _FTA_localscope=true
for /f delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /n "^" "%~2"') do (
    setlocal disabledelayedexpansion
    set _FTA_buffer=%%I
    setlocal enabledelayedexpansion
    set /a "_FTA_ubound+=1"
    endlocal & endlocal & set /a "_FTA_ubound=!_FTA_ubound!" & set %_FTA_Output%[!_FTA_ubound!]=!_FTA_buffer:*:=!
    )
for /F "delims=" %%a in ('set %_FTA_Output% 2^>nul') do endlocal & set %%a
if defined _FTA_localscope endlocal
GoTo :EOF

(this does not set any values)

::Usage Call :SimpleFileToArray OutputArray Filename
:SimpleFileToArray
set "_FTA_Output=%~1"
set "_FTA_ubound=1"
setlocal enabledelayedexpansion
set _FTA_localscope=true
for /f delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /n "^" "%~2"') do (
    setlocal disabledelayedexpansion
    set _FTA_buffer=%%I
    endlocal & set %_FTA_Output%[!_FTA_ubound!]=!_FTA_buffer:*:=!
    set /a "_FTA_ubound+=1"
    )
for /F "delims=" %%a in ('set %_FTA_Output% 2^>nul') do endlocal & set %%a
if defined _FTA_localscope endlocal
GoTo :EOF

This sets every array item to *:=

example

LinesArray[999]=*:=

To launch this function, I use this test function

:SimpleFileToArray-DEMO
Call :ClearVariablesByPrefix _FTA LinesArray
echo start SimpleFileToArray %time%
Call :SimpleFileToArray LinesArray batchsample.bat
echo end SimpleFileToArray %time%
GoTo :EOF

Solution

  • Mmm... I read several times your question (and your answer) and I still don't understand what really is your goal... So I'm going to risk posting an answer that can be entirely useless...

    This is my code:

    @echo off
    
    findstr /N "^:[^:]" test.txt | findstr /N "^"
    
    echo/
    echo Empty lines:
    echo/
    
    findstr /N "^$" test.txt
    

    The data I used as input is your code above, in your answer.

    And this is the output:

    1:1::FindAllLabels-DEMO
    2:14::FindAllLabelsFromFileLineArray
    3:24::FindAllLabelsFromFileLineArray-loop
    4:39::FindAllLabelsFromFileLineArray-skip
    5:46::TrimBeforeChar
    6:51::IsLabel
    7:58::IsLabel-loop
    8:68::IsLabel-end
    9:75::GetLabel
    10:83::GetLabel-loop
    11:92::GetLabel-end
    12:99::FileToArray
    13:108::FileToArray-arguments
    14:129::ClearVariablesByPrefix
    
    Empty lines:
    
    2:
    4:
    6:
    8:
    10:
    12:
    44:
    49:
    73:
    97:
    127:
    

    In the first group the first number is a sequential number; the second one is the original line number, and the third field are the :labels in your Batch file...

    So?

    EDIT 2023/08/20: New code added as per comments

    This code lists all functions and allows to extract any function. Check the comments in the code.

    @echo off
    setlocal EnableDelayedExpansion
    
    rem Get empty lines (plus/minus one)
    for /F "delims=:" %%a in ('findstr /N "^$" test.txt') do (
       set /A "p=%%a+1, n=%%a-1"
       set /A "prev[!p!]=1, next[!n!]=1"
    )
    
    cls
    echo Defined functions:
    rem A "function" starts in a :label preceded by an empty line
    for /F "tokens=1* delims=:" %%a in ('findstr /N "^:[^:]" test.txt') do (
       if defined prev[%%a] echo %%a-  :%%b
    )
    
    :nextFunction
    echo/
    echo/
    set /P "start=Enter line number of function to list: "
    if errorlevel 1 goto :EOF
    if not defined prev[%start%] echo ERROR & goto nextFunction
    echo/
    
    rem A "function" ends in a "goto :EOF" or "exit /B" line followed by an empty line
    for /F "delims=:" %%a in ('findstr /N /I /C:"goto :EOF" /C:"exit /B" test.txt') do (
       if %%a gtr %start% if defined next[%%a] (
          set "last=%%a"
          goto break
       )
    )
    :break
    
    setlocal DisableDelayedExpansion
    set /A start-=1
    for /F "skip=%start% delims=" %%a in ('findstr /N "^" test.txt') do (
       set "line=%%a"
       setlocal EnableDelayedExpansion
       echo(!line:*:=!
       endlocal
       for /F "delims=:" %%n in ("%%a") do (
          if %%n == %last% goto :break
       )
    )
    :break
    
    goto nextFunction
    

    NOTES:

    This is the output using your large batch file as input (and with previous notes):

    Defined functions:
    232-  :skipsection
    262-  :CommandToArray  OutputArray CommandString
    285-  :HexToDecimal-DEMO
    297-  :HexToDecimal  OutputVariable Input
    305-  :DecimalToHex  OutputVariable Input
    313-  :rtrim  OutputVariable Input
    329-  :lenByVal  OutputResult %VariableName%
    363-  :IsNumeric  Input optional Output
    384-  :ClearVariablesByPrefix  myPrefix
    389-  :LoopThroughArray  ArrayName optional lbound=x optional ubound=x "command 1" 
    426-  :SampleLoopStructure
    493-  :GetPowerSchemeContents
    504-  :GetPowerSettings
    685-  :ListPowerSubgroups  ArrayName optional lbound=x optional ubound=x "command 1" 
    702-  :ListPowerSettings  optional NOPREFIX PowerScheme(index, name or guid) PowerSubgroup(index, name or guid) optional OutputArray
    720-  :ListPowerSettingsInAllSubgroups  PowerScheme(index, name or guid) optional OutputArray
    732-  :ListPowerSettingsInAllSchemes  optional OutputArray
    746-  :ListAllPowerSubgroups  optional OutputArray
    762-  :GetPowerSchemeIndex  Powerscheme(index, name or guid) optional OutputIndex
    784-  :GetPowerSubgroupIndex  Powerscheme(index, name or guid) PowerSubgroup(index, name or guid)  optional OutputIndex
    806-  :GetPowerSettingIndex  Powerscheme(index, name or guid) PowerSubgroup(index, name or guid) PowerSetting(index, name or guid) optional OutputIndex
    826-  :GetPowerSchemeGuid  OutputGuid InputPowerSchemeName
    831-  :GetPowerSchemeName  OutputName InputPowerSchemeGuid
    836-  :GetPowerSubgroupGuid  OutputGuid InputPowerSubgroupName
    841-  :GetPowerSubgroupName  OutputName InputPowerSubgroupGuid
    846-  :GetPowerSettingIndex  OutputGuid InputPowerSettingName
    851-  :GetPowerSettingIndex  OutputName InputPowerSettingGuid
    857-  :GetDefaultSchemeName  OutputIndex OutputGuid OutputName 
    867-  :GetPowerID  [schemename.[subgroupname.[settingname]
    888-  :SetSettingACValue powercfg /CHANGE
    895-  :ShowAllPowercfgSettings
    946-  :DisableHibernation
    954-  :ShowWhatIsPreventingSleep
    963-  :EnableWakeByDevice
    967-  :GetDevicePowerProperties
    969-  :GetWakeProgrammableDevices
    985-  :GetSleepStates
    1050-  :GetSupportedSleepStates  optional OutputArray
    1061-  :GetUnsupportedSleepStates  optional OutputArray
    1072-  :GetUnsupportedSleepStatesWithReason  optional OutputArray
    1083-  :SetMonitorTimeout  optional AC-timeout-minutes optional DC-timeout-minutes
    1088-  :SetDiskTimeout  optional AC-timeout-minutes optional DC-timeout-minutes
    1103-  :GetPowerSchemeElements  ElementString PowerSetting PowerSubgroup PowerScheme
    1107-  :GetPowerSchemeValues  Powerscheme(index, name or guid) OutputArray
    1125-  :ReadInputArgument  OutputVariable Input
    1131-  :MakeStringLenght-DEMO
    1187-  :MakeStringLenght  optional RIGHTALIGNED OutputString MaxLenght Input
    1207-  :GetConsoleDimensions-DEMO
    1260-  :SetConsoleDimensions-DEMO
    1328-  :GetConsoleWidth  optional OutputVariable
    1338-  :GetConsoleHeight  optional OutputVariable
    1348-  :GetConsoleBufferWidth  optional OutputVariable
    1358-  :GetConsoleBufferHeight  optional OutputVariable
    1365-  :GetConsoleDimensions  WidthOutput HeightOutput
    1377-  :GetConsoleBufferDimensions  WidthOutput HeightOutput
    1390-  :GetCodePage  optional OutputVariable
    1400-  :GetKeyboardDelay  optional OutputVariable
    1410-  :GetKeyboardRate  optional OutputVariable
    1417-  :GetConsoleProperties
    1441-  :SetConsoleDimensions  width height
    1445-  :StringRepeat-DEMO
    1489-  :StringRepeat  OutputVariable Count Input
    1507-  :sqrt  Input optional OutputVariable
    1520-  :SquareRoot-DEMO
    1542-  :GetCirclePoint  Xvalue Radius optional Output
    1552-  :GetCirclePoint-DEMO