Introduction
This is a very basic utility and you got to have it. I was initially using GNU tools for Windows, but then I thought of writing the tools in .NET, so tail.exe is the first one.
I have looked up a number of implementations of tail in .NET, but they were not a complete port of the GNU counterpart.
The Code
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Threading;
namespace GNUtools
{
class Program
{
static int Main(string[] args)
{
int noOfLines = 0;
int returnStatus = 0;
string newline = "\n";
int charSize = encoding.IsSingleByte ? 1 : 2;
byte[] buffer = null;
bool printed = false;
string temp = string.Empty;
ParseArgs(args);
FileStream stream=null;
try
{
stream = new FileStream(fileName, FileMode.Open,
FileAccess.Read, FileShare.Write);
long endPos = stream.Length / charSize, oldPos = 0;
long posLength;
do
{
printed = false;
noOfLines = 0;
buffer = new byte[charSize];
endPos = stream.Length / charSize;
if (endPos <= oldPos) oldPos = endPos;
posLength = endPos - oldPos;
for (long pos = charSize; pos <= posLength; pos += charSize)
{
stream.Seek(-pos, SeekOrigin.End);
stream.Read(buffer, 0, charSize);
temp = encoding.GetString(buffer);
if (temp == newline)
{
noOfLines++;
}
if (noOfLines == noOfLinesWanted || pos == noOfCharsWanted)
{
buffer = new byte[endPos - stream.Position];
stream.Read(buffer, 0, buffer.Length);
Console.WriteLine(encoding.GetString(buffer));
printed = true;
oldPos = endPos;
break;
}
}
if (!printed)
{
buffer = new byte[endPos - oldPos];
stream.Seek(-1, SeekOrigin.Current);
stream.Read(buffer, 0, buffer.Length);
Console.WriteLine(encoding.GetString(buffer));
oldPos = endPos;
}
if (loop && endPos == stream.Length / charSize) Thread.Sleep(0);
} while (true);
}
catch (ArgumentException)
{
printUsage();
}
catch (IOException)
{
printUsage();
returnStatus = -5;
}
catch (Exception)
{
Console.WriteLine("Encountered some error.");
returnStatus = -1;
}
finally
{
if (stream != null)
stream.Close();
}
return returnStatus;
}
static void printUsage()
{
Console.Write("Usage : ");
Console.WriteLine("tail [OPTION]... [FILE]");
Console.WriteLine("");
Console.WriteLine("Print the last 10 lines of FILE to console.");
Console.WriteLine("");
Console.WriteLine("OPTION :");
Console.WriteLine("");
Console.WriteLine("-f : keep scanning the file till user aborts");
Console.WriteLine("-n=[no. of lines] : from end to read. Default is 10.");
Console.WriteLine("-c=[no. of chars] : from end to read.
Default is 800 ascii chars. ");
Console.WriteLine("-e=[ascii/unicode] : type of encoding to use
while reading the file. Default is ASCII.");
Console.WriteLine("");
}
static void ParseArgs(string[] args)
{
string[] option = null;
if (args.Length == 0)
{
throw new ArgumentException();
}
foreach (string arg in args)
{
option = arg.Split(new char[]{'='}, StringSplitOptions.None);
switch (option[0])
{
case "-f":
loop = true;
break;
case "-n":
noOfLinesWanted = int.Parse(option[1]);
if (noOfLinesWanted < 1 || noOfLinesWanted > 40)
noOfLinesWanted = 10;
break;
case "-c":
noOfCharsWanted = int.Parse(option[1]);
if (noOfCharsWanted < 1 || noOfCharsWanted > 800)
noOfCharsWanted = 800;
break;
case "-e":
encoding=Encoding.GetEncoding(option[1]);
break;
default:
fileName = arg;
break;
}
}
}
private static string fileName;
private static int noOfLinesWanted = 10;
private static int noOfCharsWanted = 10 * 80 * 1;
private static Encoding encoding = Encoding.ASCII;
private static bool loop = true;
}
}
To Do
There are a couple of things I would like to do:
- Read a block of data from the file. It will be more efficient.
- Use file change event if possible.
There is bound to be some testcase which will break this code. I know that, I haven't tested it properly. So just let me know those scenarios and I will hopefully try to fix it.
Also if you think there is some feature I should support, do let me know.
Updates/Fixes
- Updated to handle bad input for "
-n
" and "-c
"
Finally
Do comment about this article and rate it. :)