1

This might be an XY question, but I am trying to echo various lines from a text file.

@setlocal enableextensions enabledelayedexpansion
@echo off
set /a "line = 0"
for /f "tokens=* delims= " %%a in (file.txt) do (
    set /a "line = line+1"
    if !line!==18 set thing1=%%a
    if !line!==19 set thing2=%%a
    if !line!==20 set thing3=%%a
)
endlocal & set thing1=%thing1% & set thing2=%thing2% & set thing3=%thing3%
echo:
echo %thing1%
echo %thing2%
echo %thing3%
pause

This works well and is neat compared to others I found so I was tiring to to make it more adaptable. I can make the line numbers variable, but what if I wanted four lines, or a range of lines? So I tried to make a for loop. List all the lines:

(
    echo @setlocal enableextensions enabledelayedexpansion
    echo @echo off
    echo set /a "line = 0"
    echo for /f "tokens=* delims= " %%a in (file.txt^) do (
        echo     set /a "line = line+1"
    )>>test.bat
    for /l %%m in (1,1,10) do (
        echo     if !line!==%%m set thing%%m=%%%%a
    )>>test.bat
    echo )>>test.bat
echo      endlocal ^& )>>test.bat
for /l %%m in (1,1,10) do (
    echo     set thing%%m^=%%thing%%%%m ^&  
)>>test.bat

Then I could echo %thing(anynumber%) as I wished. This runs into problems when the for loop list need to be on the same line:

endlocal & set thing1=%thing1% & set thing2=%thing2% & set thing3=%thing3%

Instead it outputs:

endlocal & 
    set thing1=%thing%1 & 
    set thing2=%thing%2 & 
    set thing3=%thing%3 & 
    etc...

I know prompt $H can backspace, but I think that is a dead end for what I am trying here. I can't find much on reverse line feed online. Also it adds an ampersand to the final %%thing%% in the list.

Sample file.txt

This is line one
This is line two
This is line three
This is line four
This is line five
This is line six
This is line seven
This is line eight
This is line nine
This is line ten

Maybe findstr is the way to go about this. I found this and edited it to suit what I was trying to do:

:start
cls
set /p "line= Which lines?" 
for /f "tokens=*  delims=[] " %%a in ('type file.txt^|find /v /n ""') do (
    echo/%%a|findstr /l /b "%line%" >nul && echo/%%a
)
pause
goto :start

But with an input of 1 it will echo every instance beginning with "1" i.e. 1,11,12,13 etc. This seems to be nearly there. I've tried various switches from the findstr /?, but can't figure it out. If it could do input ranges too, so input line 1-5,7,12-15 would echo them 10 lines.

Compo
  • 36,585
  • 5
  • 27
  • 39
roar
  • 53
  • 6
  • To help others to assist you with your issue, could you please provide us with sample content from `file.txt` and it's respective expected content within the output file, `test.bat`. – Compo Jan 09 '20 at 13:30
  • Well, the backspace character wont help here, because when you write that character into a file, it *is* written there, it does not delete the previous character (as on the console). Anyway, I think you are over-complicating things; I wouldn't write a temporary batch-file, I'd go for an approach using something like [pseudo-arrays](https://stackoverflow.com/q/10166386)... – aschipfl Jan 09 '20 at 13:56
  • My answer here https://superuser.com/questions/1513688/how-to-echo-text-into-a-specific-line-and-column-of-a-file/1513709#1513709 regards accessing a specific line, and if combined with an array to define line numbers to compare will do the job. (The array can also define the substitution text) Regarding array population and processing: https://pastebin.com/bnD9S0Rs – T3RR0R Jan 09 '20 at 14:41

3 Answers3

2

This should do (although not with ranges):

@echo off
setlocal 

set lines=1,5,6,8
(for %%a in (%lines%) do echo %%a:)>lines.txt

for /f "tokens=1,* delims=:" %%a in ('type file.txt^|findstr /n "^"^|findstr /bg:lines.txt') do echo/%%b
del lines.txt

The first for loop builds a temporary file for findstr /g (see findstr /? for details).
The second one adds line numbers, looks them up in the file and prints the original line if the line number is in the file.

The numbers in %lines% can be delimited by any standard delimiters (TABs, SPACEs, Commas and even = (not recommended - stay with spaces and/or commas)

To expand to Ranges 5-10, you'd need to parse %lines% with some more code (hard to make that fool-proof) and "translate" to single line numbers to write into lines.txt

(We could also expand %lines% to a REGEX search string or findstr, avoiding a temporary file, but it's much easier to understand and maintain this way)

Edit: implemented a simple "range extension" (without checking for plausability):

@echo off 
setlocal 

set "lines=1,4-6,9"

(for %%a in (%lines%) do (
  echo %%a|find "-" >nul && call :range %%a || echo/%%a:
))>lines.txt

echo lines %lines% are:
for /f "tokens=1,* delims=:" %%a in ('type file.txt^|findstr /n "^"^|findstr /bg:lines.txt') do echo/%%b
del lines.txt
goto :eof

:range
for /f "tokens=1,2 delims=-" %%b in ("%1") do (
  for /l %%i in (%%b,1,%%c) do echo %%i:
)
goto :eof

Output (with your sample input file):

lines 1,4-6,9 are:
This is line one
This is line four
This is line five
This is line six
This is line nine

PS: this outputs the lines in their original order (set "lines=1,4-6,8" and set "lines=8 1 4-6" give the same output (due to how findstr /g works))

Stephan
  • 53,940
  • 10
  • 58
  • 91
  • This is good. I played around with ```echo:%lines%|findstr /r "^[0-9][0-9]$ ^[0-9][0-9]\-[0-9][0-9]$";if errorlevel 1 (goto :specified) else (goto :range)``` to try pattern match but I couldn't get it right. I ended up using a ```choice``` command. – roar Jan 10 '20 at 18:51
  • That edit is really good. Thank you. May I ask, in the first loop ```find``` looks for ```"-"``` and calls ```:range``` . Why then does it also echo out the single characters from ```%lines%``` . I thought with ```||``` it means if it does not find ```"-"``` it will echo the single characters into ```line.txt``` . But as it stands it finds ```"-"``` but still echos line 1 and line 9 into ```line.txt```, as well as line 4 through 6. – roar Jan 13 '20 at 10:49
  • 1
    if there is a `-` in the token (not the whole string!), then call :range (which echoes a line for each number within the range), else echo token. (Tokens are delimited by SPACEs, Commas,..., so the tokens above are `1`, `4-6` and `9`) – Stephan Jan 13 '20 at 11:18
0

I am not really understanding the arrays. I will have another look next week. So far I've got this based on @Stephan answer.

choice /c rs /m "RANGE MODE OR SPECIFIED MODE"
goto:%errorlevel%
:2
:specified
echo:
set /p lines=ENTER SPECIFIC LINES (seperated by spaces)? 
(for %%a in (%lines%) do echo %%a:)>lines.txt
for /f "tokens=1,* delims=:" %%a in (
    'type file.txt^|findstr /n "^"^|findstr /bg:lines.txt'
    ) do (
    echo/%%b)
del lines.txt
pause
goto :eof

:1
:range
echo:
set /p ran=ENTER RANGE (e.g. 15-25)? 
echo %ran%>range.txt
for /f "tokens=1,* delims=-" %%a in (range.txt) do (
    set line1=%%a
    set line2=%%b)
del range.txt
(for /l %%a in (%line1%,1,%line2%) do echo %%a:)>lines.txt
for /f "tokens=1,* delims=:" %%a in (
    'type file.txt^|findstr /n "^"^|findstr /bg:lines.txt'
    ) do (
    echo/%%b)
del lines.txt
pause
goto :eof

Trying to follow arrays through this link I can see when setting elements within the batch but not pointing file.txt. I tried

for /f %%a in ('type file.txt^|echo !elem[%2%]!') do echo %%a

and

      for /f "tokens=*  delims=" %%a in ('type file.txt^|find /v /n ""') do (
    echo/%%a|findstr /l /b "!elem[%2%]!" >nul && echo echo/%%a)

Anyway that is a different question, I have work through that link next week.

roar
  • 53
  • 6
0

Cuts the number of lines from the top or bottom of file.

This is similar to Unix's Tail command of which there is no Windows' equivalent.

To use

Cut

cut {t|b} {i|x} NumOfLines

Cuts the number of lines from the top or bottom of file.

t - top of the file
b - bottom of the file
i - include n lines
x - exclude n lines

Example

 cscript //nologo c:\folder\cut t i 5 < "%systemroot%\win.ini"

Copy following lines into cut.vbs

Set Arg = WScript.Arguments
set WshShell = createObject("Wscript.Shell")
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Set rs = CreateObject("ADODB.Recordset")
With rs
    .Fields.Append "LineNumber", 4 
    .Fields.Append "Txt", 201, 5000 
    .Open
    LineCount = 0
    Do Until Inp.AtEndOfStream
        LineCount = LineCount + 1
        .AddNew
        .Fields("LineNumber").value = LineCount
        .Fields("Txt").value = Inp.readline
        .UpDate
    Loop

    .Sort = "LineNumber ASC"

    If LCase(Arg(1)) = "t" then
        If LCase(Arg(2)) = "i" then
            .filter = "LineNumber < " & LCase(Arg(3)) + 1
        ElseIf LCase(Arg(2)) = "x" then
            .filter = "LineNumber > " & LCase(Arg(3))
        End If
    ElseIf LCase(Arg(1)) = "b" then
        If LCase(Arg(2)) = "i" then
            .filter = "LineNumber > " & LineCount - LCase(Arg(3))
        ElseIf LCase(Arg(2)) = "x" then
            .filter = "LineNumber < " & LineCount - LCase(Arg(3)) + 1
        End If
    End If

    Do While not .EOF
        Outp.writeline .Fields("Txt").Value

        .MoveNext
    Loop
End With

For a line count program

Set Arg = WScript.Arguments
set WshShell = createObject("Wscript.Shell")
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Do Until Inp.AtEndOfStream
    Line=Inp.readline
    Count = Count +1
Loop
outp.writeline Count