<http://www.netikka.net/tsneti/info/tscmd023.htm>
Copyright © 2003-2012 by Prof. Timo Salmi  
Last modified Wed 25-Jul-2012 19:58:44

 
Assorted NT/2000/XP/.. CMD.EXE Script Tricks
From the html version of the tscmd.zip 1cmdfaq.txt file
To the Description and the Index
 

This page is edited from the 1cmdfaq.txt faq-file contained in my tscmd.zip command line interface (CLI) collection. That zipped file has much additional material, including a number of detached .cmd script files. It is recommended that you also get the zipped version as a companion.

Please see "The Description and the Index page" for the conditions of usage and other such information.



23} How do I get the n'th, the first and the last line of a text file?

Assume the following LFN-type test file: "My test file.txt"
line 1
line 2 &()[]{}^=;!'+,`~
line 3 <>
line 4 ""

line 6 Line 5 is empty!
line 7
line 8 &()[]{}^=;!'+,`~
line 9

The easiest solution is to use SED
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  set Nth=2
  for /f "tokens=* delims=" %%f in ("%myfile_%") do (
    set myfile_=%%~sf)
  sed -n %Nth%p "%myfile_%"
  endlocal & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 2 &()[]{}^=;!'+,`~
which will give you any line you set, like the first line Nth=1.

To get the last line with sed
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  for /f "tokens=* delims=" %%f in ("%myfile_%") do (
    set myfile_=%%~sf)
  sed -n $p "%myfile_%"
  endlocal & goto :EOF

The renaming to a SFN-format is needed when the SED version usually assumed in this FAQ is used. However, if the SED (let us call it here UNXSED for distinction) from GnuWin32 Sed is used, then the LFN/SFN file name conversion is not needed.

  @echo off & setlocal enableextensions
  set N1=2
  set N2=6
  unxsed -n %N1%,%N2%p "My test file.txt"
  endlocal & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 2 &()[]{}^=;!'+,`~
line 3 <>
line 4 ""

line 6 Line 5 is empty!

With GnuWin32 Gawk the generic solution is
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  set N1=2
  set N2=6
  unxgawk "(NR>=%N1%) && (NR>=%N2%) {printf \"%%s\n\",$0}" "%myfile_%"
  endlocal & goto :EOF

To get the first line with G(nu)AWK use
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  unxgawk "NR==1 {printf \"%%s\n\",$0}" "%myfile_%"
  endlocal & goto :EOF

To get the last line with G(nu)AWK use
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  unxgawk "END{print}" "%myfile_%"
  endlocal & goto :EOF
Simple as that!

What about getting all but the last line of a file? With SED
  @echo off & setlocal enableextensions
  type "My test file.txt"|sed "$d"
  endlocal & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 1
line 2 &()[]{}^=;!'+,`~
line 3 <>
line 4 ""

line 6 Line 5 is empty!
line 7
line 8 &()[]{}^=;!'+,`~

Then about getting all but the last three lines of a file? With SED
  @echo off & setlocal enableextensions
  type "My test file.txt"|sed -e :a -e "$d;N;2,3ba" -e "P;D"
  endlocal & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 1
line 2 &()[]{}^=;!'+,`~
line 3 <>
line 4 ""

line 6 Line 5 is empty!

A Visual Basic aided command line script solution can be used for a generic solution to output the lines of a text file starting from n1 and ending with n2. It does not have the empty lines discrepancy problem of a straight script to be discussed later on.
  @echo off & setlocal enableextensions
  ::
  :: Your parameters

  set myfile_=My test file.txt
  set n1=2
  set n2=6
  ::
  :: Build a Visual Basic Script

  set vbs_=%temp%\tmp$$$.vbs
  set skip=
  findstr "'%skip%VBS" "%~f0" > "%vbs_%"
  ::
  :: Run it with Microsoft Windows Script Host Version 5.6

  cscript //nologo "%vbs_%" %n1% %n2% < "%myfile_%"
  ::
  :: Clean up

  for %%f in ("%vbs_%") do del %%f
  endlocal & goto :EOF
  '
  '............................................
  'The Visual Basic Script
  '

  set n = WScript.Arguments 'VBS
  n1 = Int(n(0)) 'VBS
  n2 = Int(n(1)) 'VBS
  '
  i = 0 'VBS
  Do While Not WScript.StdIn.AtEndOfStream 'VBS
    i = i + 1 'VBS
    If i > n2 Then Exit Do 'VBS
    str = WScript.StdIn.ReadLine 'VBS
    If i >= n1 Then 'VBS
      WScript.StdOut.WriteLine str 'VBS
    End If 'VBS
  Loop 'VBS

The output will be
C:\_D\TEST>cmdfaq
line 2 &()[]{}^=;!'+,`~
line 3 <>
line 4 ""

line 6 Line 5 is empty!
or with n1=1 and n2=1
C:\_D\TEST>cmdfaq
line 1
For getting the last line with VBS see Item #69.

Next the question arises what can be do with a pure script without aids such as VBS or sed. For one solution see the below. (For the first line one would naturally just set the line number as 1).
  @echo off
  setlocal enableextensions
  ::
  :: Choose the test file

  set myfile_=My test file.txt
  ::
  :: Get e.g. the second line.

  set Nth=2
  call :ProcGetLine "%myfile_%" %Nth%
  ::
  :: Another test. Get the last line

  call :ProcGetLastLine "%myfile_%" getLine
  echo %getLine%
  endlocal & goto :EOF
  ::
  ::==============================================================

  :ProcGetLine FileName LineNro returnText
  setlocal enableextensions disabledelayedexpansion
  set /a skip_=%2-1
  if %2 GTR 1 goto _notFirst
  for /f "tokens=* delims=" %%r in (
    'type "%~1"') do (
      echo.%%r
      endlocal & goto :EOF)
  :_notFirst
  for /f "tokens=* skip=%skip_% delims=" %%r in (
    'type "%~1"') do (
      echo.%%r
      endlocal & goto :EOF)
  ::
  ::===============================================================

  :ProcGetLastLine FileName returnText
  setlocal enableextensions disabledelayedexpansion
  for /f "tokens=* delims=" %%r in ('type "%~1"') do (
    set return_=%%r)
  endlocal & set "%~2=%return_%" & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 2 &()[]{}^=;!'+,`~
line 9

However, such a pure batch solution can be confusing because of any empty lines and also because of the special characters if one is not careful. For example if one sets Nth=5 one gets
C:\_D\TEST>cmdfaq
line 6 Line 5 is empty!
and if one sets Nth=6 one gets exactly the same! The for's skip is obviously out of synch.

There are other pure script options (but with similar problems) like
  @echo off & setlocal enableextensions
  ::
  :: Choose the test file

  set myfile_=My test file.txt
  ::
  :: Special get the first line of a file

  set /p line_=<"%myfile_%"
  echo %line_%
  ::
  :: Special get the last file of a file

  for /f "delims=" %%r in ('type "%myfile_%"') do set line_=%%r
  echo %line_%
  ::
  :: Special get the Nth line

  for %%f in ("%temp%\mytemp.txt") do if exist %%f del %%f
  set /a n_=5-1
  for /f "skip=%n_% delims=" %%r in (
    'type "%myfile_%"') do echo %%r>>"%temp%\mytemp.txt"
  set /p line_=<"%temp%\mytemp.txt"
  echo %line_%
  ::
  :: Clean up

  for %%f in ("%temp%\mytemp.txt") do if exist %%f del %%f
  endlocal & goto :EOF

The output will be
C:\_D\TEST>cmdfaq
line 1
line 9
line 6 Line 5 is empty!
It gets worse, though. If one tries to get the second line
  line 2 &()[]{}^=;!'+,`~
the method breaks down
C:\_D\TEST>cmdfaq
) was unexpected at this time.

Using Phil Robyn's referenced (see at end), faster solution we would have
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  for /f %%a in ('find /v /c "" ^< "%myfile_%"') do (
    set /a linecount=%%a)
  set /a linecount-=1
  for /f "tokens=*" %%a in (
    'more /e +%linecount% "%myfile_%"') do (
      set last_line=%%a)
  echo %last_line%
  endlocal & goto :EOF
Unfortunately, also Phil's method is suspectible to breaking down.

Alternatively, the more straight-forward
  @echo off & setlocal enableextensions enabledelayedexpansion
  ::
  set myfile_=My test file.txt
  ::
  set /a GetLineNumber=5
  for /f "tokens=* delims=" %%r in ('type "%myfile_%"') do (
    set /a LineCount_+=1
    if !LineCount_! EQU %GetLineNumber% set line_=%%r)
  echo %line_%
  ::
  endlocal & goto :EOF
The output is
C:\_D\TEST>cmdfaq
line 6 Line 5 is empty
The same problems remain, though.

So far so good(?), but one question remains, especially if the text file is big. Which is the fastest (or fast enough) method. This aspect has been worked out by Phil Robyn.
  @echo off & setlocal enableextensions enabledelayedexpansion
  ::
  set myfile_=My test file.txt
  ::
  :: Which line?

  set /a GetLineNumber=6
  ::
  :: Perform the actual task (Due to Phil Robyn)

  set /a StartAt = GetLineNumber - 1
  more /e +%StartAt% "%myfile_%"<"%temp%\mytemp.txt"
  set /p Nth_line=<"%temp%\mytemp.txt"
  ::
  :: Show the result (note the . right after echo for empty lines)

  echo.%Nth_line%
  ::
  :: Clean up

  for %%f in ("%temp%\mytemp.txt") do if exist %%f del %%f
  endlocal & goto :EOF

The output is
C:\_D\TEST>cmdfaq
line 6 Line 5 is empty
The same problems still linger, though, if you insist on a pure script solution.

To get the first line one could also use (but still having the poison character problems)
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  set /p "FirstLine="<"%myfile_%"
  echo FirstLine=%FirstLine%
  endlocal & goto :EOF
or
  @echo off & setlocal enableextensions
  set myfile_=My test file.txt
  for /f "tokens=1,* delims=:" %%a in (
    '"findstr /n . "%myfile_%"|findstr /b 1:"') do (
    set FirstLine=%%b)
  echo FirstLine=%FirstLine%
  endlocal & goto :EOF
or
  @echo off & setlocal enableextensions enabledelayedexpansion
  rem enabledelayedexpansion
  set myfile_=C:\_D\TEST\My test file.txt
  set FirstLine=
  for /f "delims=" %%i in ('type "%myfile_%"') do (
    if not defined FirstLine set FirstLine=%%i)
  echo FirstLine=%FirstLine%
  endlocal & goto :EOF

That, in fact is an offshoot of a rough method of getting the last line of a text file:
  @echo off & setlocal enableextensions enabledelayedexpansion
  rem enabledelayedexpansion
  set myfile_=C:\_D\TEST\My test file.txt
  set LastLine=
  for /f "delims=" %%i in ('type "%myfile_%"') do set LastLine=%%i
  echo LastLine=%LastLine%
  endlocal & goto :EOF

> May I have idea how can I remove the line and one line above if the char. was matched?
You might wish to study and run the following example code
  @echo off & setlocal enableextensions
  ::
  :: Make a test file

  set testfile=C:\_M\MyTest.txt
  for %%f in ("%testfile%") do if exist %%f del %%f
  for /L %%i in (1,1,3) do echo This is row 0%%i>>"%testfile%"
  echo.>>"%testfile%"
  for /L %%i in (5,1,9) do echo This is row 0%%i>>"%testfile%"
  rem type "%testfile%"
  ::
  set targetString=row 07
  ::
  :: Get the line number of the target string

  for /f "tokens=1 delims=:" %%a in ('
    findstr /n /c:"%targetString%" "%testfile%"') do (
      set lineNber=%%a)
  ::
  set /a n1=%lineNber%-2
  sed -n 1,%n1%p "%testfile%"
  set /a n2=%lineNber%+1
  sed -n %n2%,$p "%testfile%"
  ::
  for %%f in ("%testfile%") do if exist %%f del %%f
  endlocal & goto :EOF

The test file is
This is row 01
This is row 02
This is row 03

This is row 05
This is row 06
This is row 07
This is row 08
This is row 09

The output is
This is row 01
This is row 02
This is row 03

This is row 05
This is row 08
This is row 09
1) The solution avoids the empty line catch common in pure batches.
2) If there are several matches, the last one (and the last one only) counts.

The SED deleting lines in the above could also be written as
  set /a n1=%lineNber%-1
  set /a n2=%lineNber%
  <"%testfile%" sed %n1%,%n2%d

References/Comments: (If a Google message link fails try the links within the brackets.)
  Google Groups Jan 1 2004, 12:50 pm [M]
  Google Groups Jan 1 2004, 7:44 pm [M]
  Google Groups Jan 1 2004, 8:24 pm [M]
  Google Groups Jan 5 2004, 3:41 pm [M]
  Google Groups Thu, 04 Sep 2008 21:44:35 +0300 [M]
  Google Groups Sun, 30 May 2010 06:57:49 +0000 [M]
  Google Groups Mon, 23 May 2011 10:04:28 UTC