I've written a simple implementation of tail as part of reading The C Programming Language by Kernighan & Ritchie.

The question states to write tail, which prints the last n lines of its input. By default, n is 10 but should be able to be specified by writing: tail -n to print the last n lines. I don't think the program has to open and read files, as that hasn't been covered in the book as of yet.

I chose to take input as tail -n <number> and wrote the code for parsing the arguments in (hopefully) that would allow for addition of more arguments at a later date easier using the switch statement. However right now, if there were more arguments (say x and z) one could write tail -xzn 10 which would be valid, printing the last 10 lines of input - which maybe could be viewed as a problem.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXLEN 1000

int tail(char *buffer[], int n);
int read_line(char *line, int max);
void print_lines(char* buffer[], int n);

static int lines_read = 0;

int main(int argc, char* argv[]) {
  int number_of_lines = 10;
  char c;

  while(--argc > 0 && (*++argv)[0] == '-') {
    while((c = *++argv[0])) {
      switch(c) {
        case 'n':
          if(argc-1 > 0)
            number_of_lines = atoi(*(argv+1));

  char *line_buffer[number_of_lines];

  if(tail(line_buffer, number_of_lines) < 0) {
    printf("ERROR: failed to allocate memory for a line.\n");
    return -1;

  print_lines(line_buffer, number_of_lines);

  return 0;

int tail(char *buffer[], int n) {
  int characters_read = 0;
  char *p, line[MAXLEN];

  while((characters_read = read_line(line, MAXLEN)) > 0) {
    if((p = malloc(sizeof(char)*characters_read)) == NULL) {
      return -1;
    strcpy(p, line);
    buffer[lines_read++ % n] = p;

  return 0;

int read_line(char *line, int max) {
  int c, chars_read = 0;

  while((c = getchar()) != EOF && c != '\n' &&  ++chars_read < max-1)
    *line++ = c;

  *line = '\0';

  return chars_read;

void print_lines(char *buffer[], int n) {
  for(int i=0; i < (lines_read > n ? n : lines_read); i++)
    printf("%s\n", buffer[lines_read > n ? lines_read++ % n : i]);
  • \$\begingroup\$ <limits.h> has an implementation-defined LINE_MAX that you can piggyback off of instead of defining your own MAXLEN. \$\endgroup\$
    – Andrew
    Commented Jan 25, 2018 at 19:47
  • \$\begingroup\$ @AndrewPiliser: Do you have a reference for <limits.h> defining LINE_MAX? I can't seem to find it in my copy of the standard. \$\endgroup\$ Commented Jan 25, 2018 at 20:06
  • \$\begingroup\$ @JerryCoffin That might be a Linux-specific thing, I found it here: pubs.opengroup.org/onlinepubs/7908799/xsh/limits.h.html. \$\endgroup\$
    – Andrew
    Commented Jan 25, 2018 at 22:12

1 Answer 1

  • Avoid globals. lines_read is naturally suited to return lines_read.

  • tail leaks memory. You shall free the line pointed by buffer[....] before reassigning it.

  • sizeof(char) is guaranteed to be 1.

I don't remember at which point K&R offer this exercise. In any case,

  • The line-reading loop is better expressed as fgets.

  • malloc/strcpy combination is a long way to say strdup.

  • \$\begingroup\$ Is there a reason to use fgets instead of getline? \$\endgroup\$
    – Andrew
    Commented Jan 25, 2018 at 19:46
  • \$\begingroup\$ @AndrewPiliser: all the usual ones, such as it's already well documented, and tested, and everybody who knows C immediately recognizes what it is and what it does. There is another minor detail: his getline looks like if a line is too long, it'll read a single line as multiple lines, and give no warning that it's doing so. fgets can read partial lines, but gives you a warning when it does so (the buffer you read will have a new-line if and only if you read the entire line). \$\endgroup\$ Commented Jan 25, 2018 at 20:17
  • \$\begingroup\$ @AndrewPiliser I was not even sure that fgets is within the scope of this exercise. get line is definitely out. \$\endgroup\$
    – vnp
    Commented Jan 25, 2018 at 20:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.