PGTS PGTS Pty. Ltd.   ACN: 007 008 568

point Site Navigation

point Other Blog Threads



  Valid HTML 4.01 Transitional

   Stop The Internet Filter!

   No Clean Feed

   The Internet Filter Is An Ex-parrot!






PGTS Humble Blog

Thread: Tips/Tricks For Programming etc

Author Image Gerry Patterson. The world's most humble blogger
Edited and endorsed by PGTS, Home of the world's most humble blogger

Tail Command For Windows (again)


Chronogical Blog Entries:



Date: Thu, 30 Jun 2016 23:00:00 +1000

A couple of months ago I published code for a simplified version of the "tail" command as a perl script. This could be used on Windows systems which don't have tail. But if you use Windows command prompt off, you might hanker for something a bit faster.

Most Windows distributions of perl now include gcc (MinGW version) ... So you could consider writing it in native C code. This will give you a tail command that is as fast as anything can possibly be in Windows.

The code below is very similar to the perl code for tail. Rather than use "push" and "shift" it uses a ring buffer.

It isn't as fancy as the (standard) Unix tail command. There is no support for the -f option ... And it uses a fixed line length for its ring buffer. In the version below it has been set at 16K ... It probably doesn't need to be this long since it is unlikely that a real text file would contain such long lines. Like the (standard) Unix versions it implements the -N option, where N is an integer. Because we can't use getopt() processing for this the '-' character must be nestled snugly just before the integer (i.e "- N" will fail).

Also because this program uses fixed length lines, if the lines exceed the maximum fixed length, especially the lines in the tail portion, the results may not be as expected. Also, because the max length is 16K, if you entered something like:

    tail -1000000 foo.bar
In order to see the last million lines of the foo.bar file ... The program might fail to allocate 16 GB of buffer ... On the other hand if you know for certain that the maximum length of any line in foo.bar is 200 characters, then:
    tail -m 220 -1000000 foo.bar
Probably will work. The -m option sets the maximum length to 220 (added 20 for safety) and in this case it requires only a modest 200MB of buffer.

The -m option is non-standard ... But it allows you to make this tail command more efficient if you are using it for large numbers.
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>

/*
 * Simplified version of unix tail command (for windoze)
 *
 * compile and save me as tail.exe in your PATH
 *
 * Author G. Patterson
 *
 */

/* Very long line size -- Maybe make it smaller for better performance
 * (or use the -m option) */
#define LSIZ 16384

unsigned int lines = 10;
char **ring_buf;
FILE *INP;
int max_size = LSIZ;
unsigned long rec_num = 0;

/* ------------------------------------------------------------------------ */

void usage()
{
	printf("\n");
	printf("USAGE:\n");
	printf("\ttail [OPTION] FILE\n\n");
	printf("OPTION:\n");
	printf("\t-N Show last N [integer] lines of file\n");
	printf("\t-m MAX Use MAX [integer] as maximum line length (in bytes).\n");
	printf("\t-? [Or any other char] Show this screen.\n\n");
	printf("If no file specified read stdin.\n\n");
	exit(1);
}

/* ------------------------------------------------------------------------ */

/* Process options -- Can't use getopt() because we want -N [integer] */

void process_opt(int argc,char **argv)
{
	int i;
	char wrk[32];
	char *filename = NULL;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (strlen(argv[i]) >= sizeof(wrk))
				usage();
			strcpy(wrk,argv[i] + 1);
			switch (wrk[0]) {
				case 'm':
					if (strlen(wrk) == 1) {
						if ( i++ == argc -1)
							usage();
						max_size = atoi(argv[i]);
					} else {
						max_size = atoi(wrk + 1);
					}
					/* sanity check max_size */
					if (max_size < 16)
						usage();
					max_size += 2;
					break;

				default:
					lines = atoi(wrk);
					if (lines == 0)
						usage();
					break;
			}
		} else {
			if (i == argc - 1)
				filename = argv[i];
			else
				usage();
		}
	}
	if (filename) {
		if ( (INP = fopen(filename,"r") ) == NULL ) {
			fprintf(stderr,"ERROR Cannot open file: %s\n",filename);
			exit(1);
		}
	} else {
		INP = stdin;
	}
}

/* ------------------------------------------------------------------------ */

int main (int argc, char **argv)
{
	unsigned int bytes;
	int ndx,start,end;
	process_opt(argc, argv);
	/* Allocate memory to the ring buffer */
	ring_buf = malloc(sizeof(char *) * lines);
	for (ndx = 0; ndx < lines; ndx++) {
		if ((ring_buf[ndx] = malloc(max_size)) == NULL) {
			fprintf(stderr, "Cannot allocate memory - ring buffer ndx: %u",ndx);
			exit(1);
		}
	}
	ndx = 0;
	while (1) {
		/* read lines and load them into the ring buffer 
		 * allow for "\r\n" at the end of each line */
		if ( (fgets(ring_buf[ndx], max_size - 2, INP)) == NULL)
			break;
		rec_num++;
		if (++ndx >= lines)
			ndx = 0;
	}
	/* Don't disappear up my own fundamental orifice just
	 * because N is larger than the actual number of lines in the file */
	if (rec_num < (unsigned long) lines){
		ring_buf[ndx] = NULL;
		ndx = 0;
	}
	/* Display the contents of the ring buffer */
	for (start=ndx;1;start++) {
		if (start >= lines)
			start = 0;
		if (ring_buf[start] == NULL)
			break;
		if (strlen(ring_buf[start]) )
			printf("%s",ring_buf[start]);
		ring_buf[start] = NULL;
	}
	exit(0);
}

/* ------------------------------------------------------------------------ */

As you can see (in the above code) if no file is specified on the command line it will read STDIN.

The following is some examples of how you might use tail ...

EXAMPLES:

    :: Show last 10 lines of file foo.bar on \bar SMB share hosted by \\foohost
    c:\Windows>tail \\foohost\bar\logs\foobar.log
    :: Show the last 5000 lines of a file
    c:\Windows>tail -5000 \\foohost\bar\logs\foobar.log
    :: Extract the last 10000 lines from a file, using the maximum line length of 1000 and put it into C:\Temp\foo.bar.new
    c:\Windows>type \\foohost\bar\logs\foobar.log | tail -10000 -m 1000 >C:\Temp\foo.bar.new
    :: Show the last 10 lines of the file that contain the string "foo" [or "FOO" ... etc]
    c:\Windows>findstr -i "foo" \\foohost\bar\logs\foobar.log | tail

Cygwin and Unix users, will probably laugh and shake their heads ... But if you are a Windows user you can compile this with the gcc compiler that ships with Strawberry or Activestate perl and save it in your PATH as tail.exe ... If you used the default setup of Strawberry then it will have added C:\Strawberry\perl\site\bin to your PATH and would be as good place as any to store this new little utility.

Note: There was a version of "tail" as a non-standard command in the Server 2003 Windows resource kit. It can be run as compatible command in Server 2008 and Windows 7.


Other Blog Posts In This Thread:

Copyright     2016, Gerry Patterson. All Rights Reserved.