3
\$\begingroup\$

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));
          break;
      }
    }
  }  

  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;
  }

  printf("Output:\n"); 
  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]);
}
\$\endgroup\$
1
\$\begingroup\$
  • 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.

\$\endgroup\$
  • \$\begingroup\$ Is there a reason to use fgets instead of getline? \$\endgroup\$ – Andrew says Reinstate Monica Jan 25 '18 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\$ – Jerry Coffin Jan 25 '18 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 Jan 25 '18 at 20:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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