Reversing Lines in a File

Simple scripts in WSH and PowerShell make the last lines first for easy viewing

Alex Angelopoulos

March 4, 2008

6 Min Read
ITPro Today logo in a gray background | ITPro Today


 Executive Summary:

To see what happened most recently first in text-based logs, you can use the Microsoft .NET Framework's class in both Windows Script Host (WSH) and PowerShell to create a simple reversal script.

I occasionally need to reverse the order of the lines in a text file. Reversal lets me see what happened most recently first in logs, and I can even use it to roll back complex changes such as extended file copy and move operations. No tool does this exact job in Windows, but you can use the Microsoft .NET Framework's Stack class in the System.Collections namespace to make your own tool. I'll demonstrate how to use the Stack class for reversal in both Windows Script Host (WSH) and PowerShell; when I'm done, you'll have tools usable in both the cmd.exe and PowerShell console environments.

The Need for LIFO
Let's talk about the way we typically see files and why reversing the order of their lines might be useful. Due to the way files are stored, we typically understand them as streams of data. The first item into the stream is the first out of the stream; the last item put into a stream is always at the tail end of the stream. This first-in, first-out (FIFO) processing model is called a queue. FIFO is the standard way that data is handled in most simple contexts such as reading, writing, and appending to files from a script.

Although the model works well for problems ranging from recording actions in order of occurrence to processing orders in a supermarket checkout line, sometimes you need to look at the last items first. You might be interested in the most recent lines in the Windows Update log, for example. You also might log complex operations, such as moving and renaming batch files, and might want to reverse what was done—which is possible only if you step through the operations in reverse order. In this last-in, first-out (LIFO) processing, the data structure is called a stack. Just like a stack of papers, the last item you push onto a stack is the first one you can pull (or "pop") from the stack.

Reversing the Stream in VBScript
So to put a file's lines in reverse order, a script simply needs to read the file line by line and stack the lines. The file's first line ends up at the end, and the last line ends up first.

The ReverseStream.vbs script shown in Listing 1 is the simplest possible reversing script. It actually reads from standard input, so you must run it with CScript explicitly as its host. You can put a file into the standard input stream for ReverseStream.vbs in a couple ways. The simplest is to redirect input:

cscript reversestream.vbs 
Alternatively, you can use the cmd.exe type command  to echo the file to the console and then pipe it into the script, like this:
type c:windowsWindowsUpdate.log | cscript reversestream.vbs

You can save the reversed data by redirecting  ReverseStream.vbs's output to a file:
cscript reversestream.vbs  wu-reversed.log

To simplify running the script, you can create a file  named ReverseStream.cmd in the same folder as ReverseStream.vbs. The  ReverseStream.cmd file must contain the line
@cscript //Nologo %~dpn0.vbs %*

You can then just type reversestream instead  of cscript reversestream.vbs in commands such as those just shown.
To illustrate what the %~dpn0.vbs expression does,  let's look at how it works on my system, where ReverseStream.cmd and  ReverseStream.vbs are saved in the C:appsbinscripts folder (which is in my  command search path). In a batch file, % followed by a number refers to an  element in the command line used to invoke the batch file. The variable %0  means the name used to invoke the batch file. The ~, d, p, and n characters  between % and 0 are modifiers. The ~ is supposed to expand the element and  remove any surrounding quotes; it also makes the command processor treat any  following letters as special modifiers. The d expands to the drive letter for  %0, which is C: on my system. The p expands to the relative path for the  command, which is appsbinscripts on my system. Finally, the n expands to  the base name of the command, which is reversestream. So, during runtime, %~dpn0.vbs  evaluates to C:, appsbinscripts, reversestream, and .vbs, forming the  expression
c:appsbinscriptsreversestream.vbs

which is the explicit path to the script. 
I call ReverseStream.cmd a shadow script, and I  discuss this technique a little more in a blog entry that you can link to from  the Learning Path. For more information about batch parameters, you can link to  the TechNet documentation page listed in the Learning Path. 
One other technique that works if you're running  the 64-bit version of cmd.exe is to set the default WSH host to CScript by using  the command
cscript //h:cscript

This technique won't work if you're running the  32-bit version of cmd.exe. A longstanding flaw of the 32-bit command processor  is that it breaks the input pipeline for scripts hosted in external  applications such as WSH and Perl. 
Inner Workings
Let's step through how the script works. For your  reference, Table 1 includes some of the significant properties and methods of  the .NET System.Collections.Stack class. I use only a couple of these, but the  others are handy to know about if you use a stack in other situations.
In the ReverseStream.vbs script in  Listing 1, the code at callout A simply creates a System.Collections.Stack  object. Even though Stack is a .NET class, it's visible to COM scripting by  name. A subset of .NET classes is available for scripting. The "Which .NET  Classes Can Be Used from Scripts?" sidebar outlines rules of thumb for  administrators to use in finding COM-usable .NET classes and methods.
The next step is to stack the items. At callout B  in Listing 1, the script reads lines as long as input is available, and pushes  each line onto the stack. I could have used the Pop() method to pop items off  the stack until the stack is empty. If I had used that approach, it would look  like this:
Do While Stack.Count > 0  WScript.StdOut.WriteLine Stack.PopLoop

However, there's a shortcut. The script can put the  stack into an array using ToArray and then join the items by using line  endings—which is what the script does at callout C in Listing 1. This approach not  only uses shorter code but also reduces the time and processing required for  larger stacks.
The PowerShell Version
The PowerShell version of ReverseStream.vbs, Reverse-Stream.ps1  in Listing 2, works similarly. You can use it in any PowerShell pipeline to reverse  the items fed to it. You use the Get-Content cmdlet to get the contents of a file:
get-content c:windowsWindowsUpdate.log | reverse-stream

In this statement, you can use gc or type instead of get-content because gc and type are built-in aliases for the Get-Content cmdlet.
The code for the Reverse-Stream.ps1 script is  analogous to the VBScript version but even simpler. Note that if you don't have  the Reverse-Stream.ps1 script saved to a directory in your Windows search path,  you'll need to specify the full path to the script instead of just its name. 
The begin clause (which always runs when a script  starts up) creates the stack object $stack. Each time an object is read from  the input stream, the process clause runs and the object is passed in as $_.  All the process clause does is push the object onto the stack.
After  the script reaches the end of the input, the end code runs. The script doesn't  need to bother with popping the stacked items off the stack or putting them  into an array. PowerShell recognizes .NET collections like the Stack object and  takes care of it for us. All the script needs to do is push $stack out, and the  breakdown is automatically displayed.
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like