49} I need to reverse a text file. How do I do that fairly quickly?
Assume e.g. 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 shortest solution is using a
SED
one-liner (skips, however, the empty lines with the old sed version):
@echo off & setlocal enableextensions
type "My test file.txt"|sed -n "1!G;h;$p"
endlocal & goto :EOF
C:\_D\TEST>cmdfaq
line 9
line 8 &()[]{}^=;!'+,`~
line 7
line 6 Line 5 is empty!
line 4
line 3 <>
line 2 &()[]{}^=;!'+,`~
line 1
@echo off & setlocal enableextensions
unxsed "1!G;h;$!d" "My test file.txt"
endlocal & goto :EOF
C:\_D\TEST>cmdfaq
line 9
line 8 &()[]{}^=;!'+,`~
line 7
line 6 Line 5 is empty!
line 4
line 3 <>
line 2 &()[]{}^=;!'+,`~
line 1
There are other, longer solutions, which are
included for demonstration. First with
G(nu)AWK.
@echo off & setlocal enableextensions
set awk_=%temp%\tmp$$$.awk
set tmf_=%temp%\tmp$$$.txt
> "%awk_%" echo {p=length(NR)
>> "%awk_%" echo lead="000"
>> "%awk_%" echo lead=substr(lead,1,4-p)
>> "%awk_%" echo printf"%%s%%s£%%s\n",lead,NR,$0}
::
type "My test file.txt"|gawk -f "%awk_%">"%tmf_%"
type "%tmf_%"|sort /r|gawk -F£ '{printf "%%s\n",$2}'
::
for %%f in ("%tmf_%" "%awk_%") do if exist %%f del %%f
endlocal & goto :EOF
The ouput is
C:\_D\TEST>cmdfaq
line 9
line 8 &()[]{}^=;!'+,`~
line 7
line 6 Line 5 is empty!
line 4
line 3 <>
line 2 &()[]{}^=;!'+,`~
line 1
Another
version. It does not need the £ trick.
@echo off & setlocal enableextensions
set myfile_=My test file.txt
for /f "tokens=* delims=" %%f in ("%myfile_%") do (
set myfile_=%%~sf)
set tmp1_=%temp%\tmp$$$.txt
set tmp2_=%temp%\tmp$$2.txt
gawk '{printf"%%4s %%s\n",NR,$0}' "%myfile_%" > "%tmp1_%"
<"%tmp1_%" sed -e"s/^ /000/" -e"s/^ /00/" -e"s/^ /0/">"%tmp2_%"
<"%tmp2_%" sort /r | gawk '{print substr($0,6)}'
for %%f in ("%tmp1_%" "%tmp2_%") do if exist %%f del %%f
endlocal & goto :EOF
Also
@echo off & setlocal enableextensions
type "My test file.txt"|gawk "{Array[NR]=$0}END{for(x=NR;x!=0;x--)print Array[x]}"
endlocal & goto :EOF
There is a possibility of a pure script solution. This solution benefits
from a discussion in the news:alt.msdos.batch.nt as per the earlier
item #23.
This also introduced the use of FOR /L for the first time in the
original FAQ collection.
@echo off & setlocal enableextensions
:: Make a test file
set fileName=mytest.txt
for %%f in ("%fileName%" "%temp%\mytemp.txt") do if exist %%f del %%f
for %%f in (1 2 3 4 5 6 7 8 9) do echo This is line %%f>>"%fileName%"
::
:: Get the number of lines
for /f %%a in ('find /v /c "" ^< "%fileName%"') do set /a lineCount=%%a
::
:: Loop through the lines of the file from the last line to the first
for /L %%a in (%lineCount%,-1,1) do call :OutputOneLine %%a
::
:: Clean up
for %%f in ("%fileName%" "%temp%\mytemp.txt") do if exist %%f del %%f
endlocal & goto :EOF
::
@echo off & setlocal enableextensions
:: ============================================
:OutputOneLine
set /a StartAt = %1 - 1
more /e +%StartAt% "%fileName%">"%temp%\mytemp.txt"
set /p Nth_line=<"%temp%\mytemp.txt"
echo %Nth_line%
endlocal & goto :EOF
The output is
D:\TEST>cmdfaq
This is line 9
This is line 8
This is line 7
This is line 6
This is line 5
This is line 4
This is line 3
This is line 2
This is line 1
However, this solution is
not generic!
It fails if the file contains "poison characters" and/or empty lines.
(C.f. the first example file.)
A VBS-aided script can be used for a fast and generic, but
memory-intensive solution
@echo off & setlocal enableextensions
::
set myfile_=C:\_D\TEST\My test file.txt
::
:: Build a Visual Basic Script
set skip=
set vbs_=%temp%\tmp$$$.vbs
findstr "'%skip%VBS" "%~f0" > "%vbs_%"
::
:: Run it with Microsoft Windows Script Host Version 5.6
<"%myfile_%" cscript //nologo "%vbs_%"
::
:: Clean up
for %%f in ("%vbs_%") do if exist %%f del %%f
endlocal & goto :EOF
'
' The Visual Basic Script
Dim LineArray(1000) 'VBS, increase the dimension if necessary
' Read the file from StdIn into an array
n = 0 'VBS
Do While Not WScript.StdIn.AtEndOfStream 'VBS
n = n + 1 'VBS
LineArray(n) = WScript.StdIn.ReadLine 'VBS
Loop 'VBS
' Write out the array, reversed
For i = n To 1 Step -1 'VBS
WScript.Echo LineArray(i) 'VBS
Next 'VBS