29

Briefly:

I'm after the equivalent of .NET's String.Trim in C using the win32 and standard C api (compiling with MSVC2008 so I have access to all the C++ stuff if needed, but I am just trying to trim a char*).

Given that there is strchr, strtok, and all manner of other string functions, surely there should be a trim function, or one that can be repurposed...

Thanks

Orion Edwards
  • 121,657
  • 64
  • 239
  • 328

15 Answers15

23

There is no standard library function to do this, but it's not too hard to roll your own. There is an existing question on SO about doing this that was answered with source code.

Community
  • 1
  • 1
Andrew Grant
  • 58,260
  • 22
  • 130
  • 143
  • 6
    Thanks for that. I find it retarded that there's no library function (if everyone rolls their own then they're all going to mishandle unicode, etc in various different ways), but I guess it is what it is... – Orion Edwards Mar 18 '09 at 00:43
  • C's string handling is awkward, more so with Unicode. C++ patches it with std::string, but making strings natural requires a redesign. C, despite all its virtues, is far from being a perfect language. – David Thornley Mar 18 '09 at 14:17
  • 2
    Heh. C was invented in '72. Unicode didn't come along until the '90s. For most of C's history there was nothing but ASCII, and str[x]='\0'; would do the job just fine. – T.E.D. Mar 18 '09 at 16:46
  • 1
    This has nothing to do with C. It has everything to do with the standard library being incompetent. – James M. Lay Feb 22 '22 at 20:46
19

This made me want to write my own - I didn't like the ones that had been provided. Seems to me there should be 3 functions.

char *ltrim(char *s)
{
    while(isspace(*s)) s++;
    return s;
}

char *rtrim(char *s)
{
    char* back = s + strlen(s);
    while(isspace(*--back));
    *(back+1) = '\0';
    return s;
}

char *trim(char *s)
{
    return rtrim(ltrim(s)); 
}
JRL
  • 76,767
  • 18
  • 98
  • 146
  • 1
    i tried the other implementations mentioned in this thread..it worked fine for a while until i suddenly got a seg fault.. i spent a lot of time trying to debug and found out that it was indirectly caused by the trimming code... i tried your version and the seg faults went away :) Thanks! (though i still don't understand why the seg faults occurred) – Sadhir Jul 18 '11 at 20:21
  • 2
    edit: the person reviewing my code suggested i check for NULL input values .. maybe that is something you might want to add – Sadhir Jul 18 '11 at 21:36
  • 1
    `ltrim` returns a pointer that's not suitable for passing to `free`. If you use it (or, of course, `trim`), be sure you hold onto the original pointer -- especially don't try something like `s = trim(s);` without having `s` stored elsewhere first. – cHao Dec 25 '11 at 15:30
  • 3
    What's stopping rtrim from going right past the start of the string? Looks unsafe to me... – Joe Teague Aug 23 '21 at 02:19
13

You can use the standard isspace() function in ctype.h to achieve this. Simply compare the beginning and end characters of your character array until both ends no longer have spaces.

"spaces" include:

' ' (0x20) space (SPC)

'\t' (0x09) horizontal tab (TAB)

'\n' (0x0a) newline (LF)

'\v' (0x0b) vertical tab (VT)

'\f' (0x0c) feed (FF)

'\r' (0x0d) carriage return (CR)

although there is no function which will do all of the work for you, you will have to roll your own solution to compare each side of the given character array repeatedly until no spaces remain.

Edit:

Since you have access to C++, Boost has a trim implementation waiting for you to make your life a lot easier.

Community
  • 1
  • 1
John T
  • 23,735
  • 11
  • 56
  • 82
7

Surprised to see such implementations. I usually do trim like this:

char *trim(char *s) {
    char *ptr;
    if (!s)
        return NULL;   // handle NULL string
    if (!*s)
        return s;      // handle empty string
    for (ptr = s + strlen(s) - 1; (ptr >= s) && isspace(*ptr); --ptr);
    ptr[1] = '\0';
    return s;
}

It is fast and reliable - serves me many years.

qrdl
  • 34,062
  • 14
  • 56
  • 86
  • 3
    Oh, i think that can cause a buffer underrun, consider this: char buffer[] = " "; trim(buffer); Then you at least reading buffer[-1], and if it is randomly a white space, you'll even write out side of your buffer. – quinmars Mar 18 '09 at 16:06
  • Nice one! I've added extra check for this. Will check my production code as well :) – qrdl Mar 18 '09 at 16:34
4
/* Function to remove white spaces on both sides of a string i.e trim */

void trim (char *s)
{
    int i;

    while (isspace (*s)) s++;   // skip left side white spaces
    for (i = strlen (s) - 1; (isspace (s[i])); i--) ;   // skip right side white spaces
    s[i + 1] = '\0';
    printf ("%s\n", s);
}
Mahant
  • 41
  • 1
3
#include "stdafx.h"
#include <string.h>
#include <ctype.h>

char* trim(char* input);


int _tmain(int argc, _TCHAR* argv[])
{
    char sz1[]="  MQRFH  ";
    char sz2[]=" MQRFH";
    char sz3[]="  MQR FH";
    char sz4[]="MQRFH  ";
    char sz5[]="MQRFH";
    char sz6[]="M";
    char sz7[]="M ";
    char sz8[]=" M";
    char sz9[]="";
    char sz10[]="        ";

    printf("sz1:[%s] %d\n",trim(sz1), strlen(sz1));
    printf("sz2:[%s] %d\n",trim(sz2), strlen(sz2));
    printf("sz3:[%s] %d\n",trim(sz3), strlen(sz3));
    printf("sz4:[%s] %d\n",trim(sz4), strlen(sz4));
    printf("sz5:[%s] %d\n",trim(sz5), strlen(sz5));
    printf("sz6:[%s] %d\n",trim(sz6), strlen(sz6));
    printf("sz7:[%s] %d\n",trim(sz7), strlen(sz7));
    printf("sz8:[%s] %d\n",trim(sz8), strlen(sz8));
    printf("sz9:[%s] %d\n",trim(sz9), strlen(sz9));
    printf("sz10:[%s] %d\n",trim(sz10), strlen(sz10));

    return 0;
}

char *ltrim(char *s) 
{     
    while(isspace(*s)) s++;     
    return s; 
}  

char *rtrim(char *s) 
{     
    char* back;
    int len = strlen(s);

    if(len == 0)
        return(s); 

    back = s + len;     
    while(isspace(*--back));     
    *(back+1) = '\0';     
    return s; 
}  

char *trim(char *s) 
{     
    return rtrim(ltrim(s));  
} 

Output:

sz1:[MQRFH] 9
sz2:[MQRFH] 6
sz3:[MQR FH] 8
sz4:[MQRFH] 7
sz5:[MQRFH] 5
sz6:[M] 1
sz7:[M] 2
sz8:[M] 2
sz9:[] 0
sz10:[] 8
Maddy
  • 31
  • 2
2
void ltrim(char str[PATH_MAX])
{
        int i = 0, j = 0;
        char buf[PATH_MAX];
        strcpy(buf, str);
        for(;str[i] == ' ';i++);

        for(;str[i] != '\0';i++,j++)
                buf[j] = str[i];
        buf[j] = '\0';
        strcpy(str, buf);
}
Nitinkumar Ambekar
  • 969
  • 20
  • 39
2

I like it when the return value always equals the argument. This way, if the string array has been allocated with malloc(), it can safely be free() again.

/* Remove leading whitespaces */
char *ltrim(char *const s)
{
        size_t len;
        char *cur;

        if(s && *s) {
                len = strlen(s);
                cur = s;

                while(*cur && isspace(*cur))
                        ++cur, --len;

                if(s != cur)
                        memmove(s, cur, len + 1);

        }

        return s;
}

/* Remove trailing whitespaces */
char *rtrim(char *const s)
{
        size_t len;
        char *cur;

        if(s && *s) {
                len = strlen(s);
                cur = s + len - 1;

                while(cur != s && isspace(*cur))
                        --cur, --len;

                cur[isspace(*cur) ? 0 : 1] = '\0';
        }

        return s;
}

/* Remove leading and trailing whitespaces */
char *trim(char *const s)
{
        rtrim(s);  // order matters
        ltrim(s);

        return s;
}
Abdul Rahman
  • 2,097
  • 4
  • 28
  • 36
Philip
  • 5,795
  • 3
  • 33
  • 68
1
static inline void ut_trim(char * str) {
   char * start = str;
   char * end = start + strlen(str);

   while (--end >= start) {   /* trim right */
      if (!isspace(*end))
         break;
   }
   *(++end) = '\0';

   while (isspace(*start))    /* trim left */
      start++;

   if (start != str)          /* there is a string */
      memmove(str, start, end - start + 1);
}
Tim Stone
  • 19,119
  • 6
  • 56
  • 66
roy
  • 11
  • 1
0

How about this... It only requires one iteration over the string (doesn't use strlen, which iterates over the string). When the function returns you get a pointer to the start of the trimmed string which is null terminated. The string is trimmed of spaces from the left (until the first character is found). The string is also trimmed of all trailing spaces after the last nonspace character.

char* trim(char* input) {
    char* start = input;
    while (isSpace(*start)) { //trim left
        start++;
    }

    char* ptr = start;
    char* end = start;
    while (*ptr++ != '\0') { //trim right
        if (!isSpace(*ptr)) { //only move end pointer if char isn't a space
            end = ptr;
        }
    }

    *end = '\0'; //terminate the trimmed string with a null
    return start;
}

bool isSpace(char c) {
    switch (c) {
        case ' ':
        case '\n':
        case '\t':
        case '\f':
        case '\r':
            return true;
            break;
        default:
            return false;
            break;
    }
}
  • This modifies the input string in-place. While it seems like the performance would be great, I wouldn't use this unless I absolutely had to, as modifying the string in-place may be unexpected, as would be returning a start pointer into the middle of the existing string. The caller would have to know not to delete the original string until the "trimmed" one is no longer needed – Orion Edwards Nov 05 '13 at 20:49
0
/* iMode 0:ALL, 1:Left, 2:Right*/
char* Trim(char* szStr,const char ch, int iMode)
{
    if (szStr == NULL)
        return NULL;
    char szTmp[1024*10] = { 0x00 };
    strcpy(szTmp, szStr);
    int iLen = strlen(szTmp);
    char* pStart = szTmp;
    char* pEnd = szTmp+iLen;
    int i;
    for(i = 0;i < iLen;i++){
        if (szTmp[i] == ch && pStart == szTmp+i && iMode != 2)
            ++pStart;
        if (szTmp[iLen-i-1] == ch && pEnd == szTmp+iLen-i && iMode != 1)
            *(--pEnd) = '\0';
    }
    strcpy(szStr, pStart);
    return szStr;
}
0

Here's my implementation, behaving like the built-in string functions in libc (that is, it expects a c-string, it modifies it and returns it to the caller).

It trims leading spaces & shifts the remaining chars to the left, as it parses the string from left to right. It then marks a new end of string and starts parsing it backwards, replacing trailing spaces with '\0's until it finds either a non-space char or the start of the string. I believe those are the minimum possible iterations for this particular task.

// ----------------------------------------------------------------------------
// trim leading & trailing spaces from string s (return modified string s)
// alg:
// - skip leading spaces, via cp1
// - shift remaining *cp1's to the left, via cp2
// - mark a new end of string
// - replace trailing spaces with '\0', via cp2
// - return the trimmed s
//
char *s_trim(char *s)
{
    char *cp1;                              // for parsing the whole s
    char *cp2;                              // for shifting & padding

    // skip leading spaces, shift remaining chars
    for (cp1=s; isspace(*cp1); cp1++ )      // skip leading spaces, via cp1
        ;
    for (cp2=s; *cp1; cp1++, cp2++)         // shift left remaining chars, via cp2
        *cp2 = *cp1;
    *cp2-- = 0;                             // mark new end of string for s

    // replace trailing spaces with '\0'
    while ( cp2 > s && isspace(*cp2) )
        *cp2-- = 0;                         // pad with '\0's

    return s;
}
Harry K.
  • 560
  • 3
  • 7
-2

Not the best way but it works

char* Trim(char* str)
{
    int len = strlen(str);
    char* buff = new char[len];
    int i = 0;
    memset(buff,0,len*sizeof(char));
    do{
        if(isspace(*str)) continue;
        buff[i] = *str; ++i;
    } while(*(++str) != '\0');
    return buff;
}
Baranovskiy Dmitry
  • 463
  • 2
  • 5
  • 23
-4
void inPlaceStrTrim(char* str) {
    int k = 0;
    int i = 0;
    for (i=0; str[i] != '\0';) {
        if (isspace(str[i])) {
            // we have got a space...
            k = i;
            for (int j=i; j<strlen(str)-1; j++) {
                str[j] = str[j+1];
            }
            str[strlen(str)-1] = '\0';
            i = k; // start the loop again where we ended..
        } else {
            i++;
        }
    }
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
-6

Easiest thing to do is a simple loop. I'm going to assume that you want the trimmed string returned in place.

char *
strTrim(char * s){
    int ix, jx;
    int len ;
    char * buf 
    len = strlen(s);  /* possibly should use strnlen */
    buf = (char *) malloc(strlen(s)+1);

    for(ix=0, jx=0; ix < len; ix++){
       if(!isspace(s[ix]))
          buf[jx++] = s[ix];

    buf[jx] = '\0';
    strncpy(s, buf, jx);  /* always looks as far as the null, but who cares? */
    free(buf);            /* no good leak goes unpunished */
    return s;             /* modifies s in place *and* returns it for swank */
 }

This gets rid of embedded blanks too, if String.Trim doesn't then it needs a bit more logic.

Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • It's hardly "modifying in place" if you malloc a new buffer and then copy it back in! – Andrew Grant Mar 18 '09 at 00:50
  • it's modified in place as far as the interface is concerned. – Charlie Martin Mar 18 '09 at 03:07
  • yeah, good luck selling that one in your next interview :) – Andrew Grant Mar 18 '09 at 06:45
  • Why so complex? Why not just loop from line to first non-empty character and replace spaces with 0? Very inefficient piece of code. That's Java way of coding, not a C one. – qrdl Mar 18 '09 at 07:40
  • @Andrew, I don't go to interviews, I give interviews. @qrdl, then you'd have an array of char that started with \0. This in C is called a "zero length string". – Charlie Martin Mar 18 '09 at 17:24
  • One of the truly fascinating things about having written C for 30 years is that there's always someone to tell me I don't know what I'm doing. They almost invariably mean *they* don't know what I'm doing. – Charlie Martin Mar 18 '09 at 21:33
  • You're right - I have no idea what you're doing using malloc for this :) Look, my point is that it's a poor implementation and certainly not 'in-place'. End of story. Smug replies just make you look like the subject of thedailywtf.com story. Let's move on. – Andrew Grant Mar 18 '09 at 22:17
  • Ah. That's easy. It's the only way to build this that (a) isn't sensitive to the size of the buffer and in particular doesn't impose a length limit on the input, (b) can be done in a single pass, and (c) doesn't return malloc'ed memory. – Charlie Martin Mar 18 '09 at 22:58
  • 3
    malloc prevents an in-place function from being sensitive to input-sizes? Sorry, that's just wrong. Calling strlen twice lost you the single-pass argument too. Despite being slower and longer your function doesn't actually even work - when does a trim function ever remove spaces between words? – Andrew Grant Mar 19 '09 at 01:37
  • You save the length of the string into `len` (which is the wrong kind of variable, it should be of type `size_t`), but rather than using `len` for the dynamic memory allocation, you just call `strlen` again. You also suggest the use of non-standard function `strnlen`. It removes "embedded blanks"?! You also have a syntax error (you're missing the closing brace for your `for` loop). You cast the return value from `malloc`, and you don't even check for `NULL`. – dreamlax Feb 14 '10 at 20:36