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