Skip to main content
Code Review

Return to Question

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link
Source Link
coderodde
  • 31.8k
  • 15
  • 77
  • 202

A trivial command line utility for trimming whitespace from lines in C - follow-up 3

The previous iteration at A trivial command line utility for trimming whitespace from lines in C - follow-up 2

Now my code looks like:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define HELP_FLAG "-h"
#define VERSION_FLAG "-v"
#define FLAG_DESC "%-5s"
#define INITIAL_BUFFER_SIZE 64
/*******************************************************************************
* This routine removes all leading and trailing whitespace from a string, *
* doing that in-place and using only one pass over the string. *
*******************************************************************************/
static char* trim_inplace(char* start)
{
 while (isspace(*start))
 {
 ++start;
 }
 int whitespace_begin_index = -1;
 // At this point, we have dealt with leading whitespace.
 for (int index = 0; start[index]; ++index)
 {
 if (!isspace(start[index]))
 {
 whitespace_begin_index = -1;
 }
 else if (whitespace_begin_index == -1)
 {
 whitespace_begin_index = index;
 }
 }
 if (whitespace_begin_index != -1)
 {
 // Cut the trailing whitespace off.
 start[whitespace_begin_index] = '0円';
 }
 return start;
}
/*******************************************************************************
* Attempts to expand the line buffer. If succeeded, returns the pointer to the *
* line buffer. Otherwise NULL is returned. *
*******************************************************************************/
static char* try_expand(char* buffer, int* p_buffer_length)
{
 *p_buffer_length *= 2;
 return realloc(buffer, *p_buffer_length);
}
/*******************************************************************************
* Processes a single line and handles everything needed for dealing with lines *
* of arbitrary length. *
*******************************************************************************/
static void process_line(char** p_buffer, int* p_buffer_length, FILE* file)
{
 int chars_read = 0;
 for (;;)
 {
 // The delta is for appending the next text chunk at correct position.
 int delta = chars_read > 0;
 char* ret = fgets(*p_buffer + chars_read - delta,
 *p_buffer_length - chars_read + delta,
 file);
 if (!ret)
 {
 return;
 }
 // Find out whether we have a newline character, which would imply that
 // we have an entire line read.
 for (int i = 0; i < *p_buffer_length; ++i)
 {
 if ((*p_buffer)[i] == '\n')
 {
 (*p_buffer)[i] = '0円';
 puts(trim_inplace(*p_buffer));
 return;
 }
 }
 chars_read = *p_buffer_length;
 char* new_buffer;
 // Once here, the current line does not fit in 'p_buffer'. Expand the
 // array by doubling its capacity.
 if (!(new_buffer = try_expand(*p_buffer, p_buffer_length)))
 {
 perror("Could not expand the line buffer");
 free(*p_buffer);
 exit(EXIT_FAILURE);
 }
 else
 {
 *p_buffer = new_buffer;
 }
 }
}
/*******************************************************************************
* Processes a file. *
*******************************************************************************/
static void process_file(char** p_buffer, int* p_buffer_length, FILE* file)
{
 while (!feof(file))
 {
 process_line(p_buffer, p_buffer_length, file);
 }
}
/*******************************************************************************
* If name contains directories, gets rid of them and returns a sole name of *
* the executable file. *
*******************************************************************************/
static const char* get_short_program_name(const char* name)
{
 size_t len = strlen(name);
 const char* ret = name;
 for (size_t i = 0; i < len; ++i)
 {
 if (name[i] == '/')
 {
 ret = &name[i + 1];
 }
 }
 return ret; 
}
/*******************************************************************************
* Prints the help message and exits. *
*******************************************************************************/
static void print_help(const char* program_name)
{
 printf("Usage: %s [" HELP_FLAG "] [" VERSION_FLAG "] " \
 "[FILE1, [FILE2, [...]]]\n" \
 " " FLAG_DESC " Print this help message and exit.\n" \
 " " FLAG_DESC " Print the version message and exit.\n" \
 " If no files specified, reads from standard input.\n",
 get_short_program_name(program_name),
 HELP_FLAG,
 VERSION_FLAG);
}
/*******************************************************************************
* Prints the version string. *
*******************************************************************************/
static void print_version()
{
 printf("trim 1.6180\n" \
 "By Rodion \"rodde\" Efremov 10.04.2015 Helsinki\n");
}
/*******************************************************************************
* Prints the erroneous flag. *
*******************************************************************************/
static void print_bad_flag(const char* flag)
{
 printf("Unknown flag \"%s\"\n", flag);
}
/*******************************************************************************
* Checks the flags. *
*******************************************************************************/
static void check_flags(int argc, char** argv)
{
 int c;
 while ((c = getopt(argc, argv, "vh")) != -1)
 {
 switch (c) 
 {
 case 'v':
 print_version();
 exit(EXIT_SUCCESS);
 break;
 case 'h':
 print_help(argv[0]);
 exit(EXIT_SUCCESS);
 break;
 case '?':
 print_bad_flag((char*) &optopt);
 exit(EXIT_FAILURE);
 break;
 default:
 abort();
 }
 }
}
/*******************************************************************************
* The entry point for a trivial line trimmer. *
*******************************************************************************/
int main(int argc, char** argv)
{
 check_flags(argc, argv);
 int buffer_length = INITIAL_BUFFER_SIZE;
 char* buffer = malloc(buffer_length);
 if (argc < 2)
 {
 // If realloc changes the location of memory, we need to know this.
 process_file(&buffer, &buffer_length, stdin);
 fclose(stdin);
 free(buffer);
 return EXIT_SUCCESS;
 }
 for (int i = 1; i < argc; ++i)
 {
 FILE* file = fopen(argv[i], "r");
 if (!file)
 {
 perror("Error opening a file");
 return (EXIT_FAILURE);
 }
 process_file(&buffer, &buffer_length, file);
 fclose(file);
 }
 free(buffer);
}

I have done the following:

  1. Boolean stuff removed as it is no longer used.
  2. Whenever printing the help message, the actual name of the binary is used.
  3. Used getopt for parsing command line arguments.
  4. Fixed size_t versus int warnings.
lang-c

AltStyle によって変換されたページ (->オリジナル) /