Using ColdFusion to get the end of a file

This post is more than 2 years old.

A week or so ago I wrote a blog entry on converting ColdFusion logs to RSS feeds (ColdFusion Logs to RSS). In the article I converted lines of a log file into an RSS feed. I mentioned that it was a bit silly to convert the beginning of a log file into RSS since new data is added to the end of a file. How can we grab the end of a file?

One way would be to simply read in the entire file and just create a new array from the last 10 (or whatever) lines. Consider:

<cfset l = "/Users/ray/Desktop/cfserver.log.1">

<cfset lines = []> <cfloop index="l" file="#l#"> <cfset arrayAppend(lines,l)> </cfloop> <cfset tail = []> <cfloop index="x" from="#arrayLen(lines)#" to="#max(arrayLen(lines)-10+1,1)#" step="-1"> <cfset arrayPrepend(tail, lines[x])> </cfloop> <cfdump var="#tail#">

I begin by creating a variable, l, that points to my file. Next, I use a loop to read ine ach line of the file. This is new syntax that was added to ColdFusion 8. Notice I don't have to worry about end of line markers or anything like that. It just plain works.

Once done, I create a new array, tail, and populate it by reading backwards from the larger array. When done you get a nice array of lines representing the end of the file.

The file I used was a bit large, 13 megs, so the operation takes a few seconds to run. Could we do it quicker? In the dusty, foggy part of my brain I remember using some Java about 10 years ago to read in the file backwards. I did some digging in the API docs and figured out that I probably needed RandomAccessFile. It lets you seek to any position in a file and read in data. Using that, I wrote up a simple UDF that seeks, and then steps back one character a time. This is not very safe - I should probably try/catch the read, but it seemed to work well enough.

<cfscript> function tailFile(filename) { var line = ""; var lines = ""; var theFile = createObject("java","java.io.File").init(filename); var raFile = createObject("java","java.io.RandomAccessFile").init(theFile,"r"); var pos = theFile.length(); var c = ""; var total = 10;
if(arrayLen(arguments) gte 2) total = arguments[2];
raFile.seek(pos-1);

while( (listLen(line,chr(10)) &lt;= total) && pos &gt; -1) {
	c = raFile.read();
	//if(c != -1) writeOutput("#c#=" & chr(c) & "&lt;br/&gt;");
	if(c != -1) line &= chr(c);
	raFile.seek(pos--);	
}

line = reverse(line);
lines = listToArray(line, chr(10));
arrayDeleteAt(lines,1);
return lines;

} </cfscript>

<cfset l = "/Users/ray/Desktop/cfserver.log.1"> <cfdump var="#tailFile(l)#">

What was interesting was the speed comparison. Using cftimer, I checked them both. Check out the results:

Pretty significant difference there. (And yes, that's ColdFire in action there.)

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Mark Stephens posted on 4/14/2009 at 6:01 PM

What about using the linux tail command via CFEXECUTE, this works fine with very large files..

http://www.computerhope.com...

Comment 2 by Raymond Camden posted on 4/14/2009 at 6:03 PM

Won't work tell well on Windows. ;)

Comment 3 by JC posted on 4/16/2009 at 1:59 AM

The code I use for one app is basically this (with foo being the file and crlf being a line break):

#listgetat(foo,listlen(foo,crlf),crlf)#

but didn't CF8 add some new functions that are better for this?

FileOpen()
FileRead()
FileReadLine()
FileIsEOF()
FileClose()

Comment 4 by Srinivas posted on 12/7/2011 at 12:31 AM

Ray,

You saved again so much time.
Pls post in CFLib.org

Thanks

Comment 5 by Raymond Camden posted on 12/7/2011 at 12:54 AM

In CF9 you do not need this anymore. We added fileSeek().

Comment 6 by Oye posted on 3/7/2012 at 7:24 PM

Hi Ray,

Below error I am getting in CFMX 6.1.
When I dump rafile, seek method is there but not invoking.Any clues.

The selected method seek was not found.

Either there are no methods with the specified method name and argument types, or the method seek is overloaded with arguments types that ColdFusion can't decipher reliably. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity.

Comment 7 by Raymond Camden posted on 3/7/2012 at 7:47 PM

Maybe your JRE is old enough to not include the Seek method. Best I can recommend is check your JRE version and then check the JavaDocs for that.