65.9K
CodeProject is changing. Read more.
Home

linux "tail -f" Command in C#.NET

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.78/5 (8 votes)

Aug 31, 2006

viewsIcon

64112

downloadIcon

948

Prints last 10 lines of a file

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";//Environment.NewLine;??
            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;  	// if file's content is 
							//deleted, reset position
                    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; //10 lines, 80 chars per line,
						   //1 ascii char 
        private static Encoding encoding = Encoding.ASCII;
        private static bool loop = true;
    }
}

To Do

There are a couple of things I would like to do:

  1. Read a block of data from the file. It will be more efficient.
  2. 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. :)