6
\$\begingroup\$

I was practicing some C and decided to write this simple command line utility for stripping leading and trailing white-space characters.

Note: see the next iteration at A trivial command line utility for trimming whitespace from lines in C - follow-up

The code:

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

#define LINE_LENGTH 8096

#define HELP_MESSAGE "Usage: trim [FILE1, [FILE2, [...]]]\n" \
                     "    If no files specified, reads from standard input.\n"

#define VERSION_MESSAGE "trim 1.6\n" \
                        "By Rodion \"rodde\" Efremov. 07.04.2015 Helsinki\n"

#define HELP_FLAG "-h"
#define VERSION_FLAG "-v"

/*******************************************************************************
* This routine removes all leading and trailing whitespace from a string,      *
* doing that in-place. (                                                       *
********************************************************************************/
static char* trim_inplace(char *const start) 
{
    size_t leading_ws_chars = 0;
    size_t trailing_ws_chars = 0;
    size_t len;
    size_t i;

    // Find amount of leading whitespace characters.
    while (isspace(*(start + leading_ws_chars)))
    {
        ++leading_ws_chars;
    }

    // Find the length of the entire string.
    len = leading_ws_chars;

    while (start[len]) 
    {
        ++len;
    }

    if (len == leading_ws_chars)
    {
        // Empty string.
        start[0] = '\0';
    }

    // Here 'start[len] == NULL'.
    while (isspace(*(start + len - 1 - trailing_ws_chars))) 
    {
        ++trailing_ws_chars;
    }

    // Shift the text to the left.
    for (i = 0; i < len - leading_ws_chars - trailing_ws_chars; ++i) 
    {
        start[i] = start[i + leading_ws_chars];
    }

    // Terminate.
    start[len - leading_ws_chars - trailing_ws_chars] = '\0';
    return start;
}

/*******************************************************************************
* Processes a file.                                                            *
*******************************************************************************/
static void process_file(FILE *const file) 
{
    char line[LINE_LENGTH];

    if (file == NULL) 
    {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    while (fgets(line, LINE_LENGTH, file) != NULL) 
    {
        printf("%s\n", trim_inplace(line));
    }
}

/*******************************************************************************
* Prints the help message and exits.                                           *
*******************************************************************************/
static void print_help() 
{
    printf(HELP_MESSAGE);
    exit(EXIT_SUCCESS);
}

/*******************************************************************************
* Prints the version string.                                                   *
*******************************************************************************/
static void print_version() 
{
    printf(VERSION_MESSAGE);
    exit(EXIT_SUCCESS);
}

/*******************************************************************************
* Checks the flags.                                                            *
*******************************************************************************/
static void check_flags(int argc, char** argv) 
{
    size_t i;

    for (i = 1; i < argc; ++i) 
    {
        if (strcmp(argv[i], HELP_FLAG) == 0) 
        {
            print_help();
        }
        else if (strcmp(argv[i], VERSION_FLAG) == 0) 
        {
            print_version();
        }
    }
}

/*******************************************************************************
* The entry point for a trivial line trimmer.                                  *
*******************************************************************************/
int main(int argc, char** argv) 
{
    size_t i;
    FILE* file;

    check_flags(argc, argv);

    if (argc < 2) 
    {
        process_file(stdin);
        return EXIT_SUCCESS;
    }
    else 
    {
        for (i = 1; i < argc; ++i) 
        {
            file = fopen(argv[i], "r");

            if (!file) 
            {
                perror("Error opening a file");
                return (EXIT_FAILURE);
            }

            process_file(file);
        }
    }

    return (EXIT_SUCCESS);
}

My question: how to make this utility conform to GNU style of doing software?

\$\endgroup\$
1

1 Answer 1

6
\$\begingroup\$

I can't help you with GNU style, but here are some things that may help you improve your code.

Close files when you are done with them

The program will automatically close files when the program terminates, but it's good to close them as soon as you're done with them. This helps prevent the problem of running out of file handles.

Simplify the trim_inplace function

The trim_inplace function can be simplfified considerably. One possibility is this:

static char* trim_inplace(char *start) 
{
   // handle empty line per @Snowbody's commment
    if (strlen(start) == 0)  
        return start;

    for (char *end = &start[strlen(start)-1]; isspace(*end); --end) 
        *end = '\0';
    while (isspace(*start))
        ++start;
    return start;
}

Don't use const if you don't mean it

The program declares this function:

static char* trim_inplace(char *const start) 

But that doesn't make much sense. It says "I can alter the characters, but I won't alter the pointer" but that doesn't really make much sense because the pointer is passed by value anyway. The same is true for the process_file argument.

See this SO question for details on that latter point.

Consider handling arbitrarily long lines

As the program is currently written, it has an arbitrary limit of 8096 characters for a line. If I have a line with 8094 spaces followed by the string "ab", the program will trim the spaces, but put "a" and "b" on separate lines in the output. The program could easily be rewritten to handle character at a time input and eliminate the arbitrary line limit.

Eliminate return EXIT_SUCCESS at the end of main

For a long time now, the C language says that finishing main will implicitly generate the equivalent to return EXIT_SUCCESS. For that reason, you should eliminate that line from your code.

\$\endgroup\$
6
  • \$\begingroup\$ Can you elaborate on handling arbitrarily long lines? Any link? \$\endgroup\$
    – coderodde
    Commented Apr 7, 2015 at 14:40
  • \$\begingroup\$ @coderodde: if you use a state machine and handle the input one character at a time, you only need a buffer large enough to hold a single character and will no longer have an arbitrary limit on line size. \$\endgroup\$
    – Edward
    Commented Apr 7, 2015 at 14:45
  • \$\begingroup\$ Doesn't this trim_inplace() run off the end if you have a line that is completely space characters? (it sets *end to the first nonspace before *start , and then sets `*start* to the forst nonspace after the end of the string. \$\endgroup\$
    – Snowbody
    Commented Apr 9, 2015 at 13:46
  • \$\begingroup\$ @Snowbody: no, that situation is handled correctly. However, if trim_inplace is given an empty string, then it does indeed start by examining the memory location before start. I'll amend the code to handle that scenario. \$\endgroup\$
    – Edward
    Commented Apr 9, 2015 at 14:37
  • \$\begingroup\$ okay my interpretation of it is wrong. What makes the loop terminate when *end reaches *start? \$\endgroup\$
    – Snowbody
    Commented Apr 9, 2015 at 16:09

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.