7

Is there a standard C library function to escape C-strings?

For example, if I had the C string:

char example[] = "first line\nsecond line: \"inner quotes\"";

And I wanted to print

"first line\nsecond line: \"inner quotes\""

Is there a library function that will do that transformation for me? Rolling my own just seems a little silly.

Bonus points if I can give it a length to escape (so it stops before or beyond the \0).

rampion
  • 87,131
  • 49
  • 199
  • 315
  • Stopping beyond the `\0` seems dangerous, you better be 100% sure that the length is valid or chaos will ensue. – bta Apr 27 '10 at 22:13
  • useful though when I want to display some binary data semi-safely. – rampion Apr 28 '10 at 00:09

7 Answers7

6

If you were writing GPL stuff you might use http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob;f=lib/quotearg.c;hb=HEAD

pixelbeat
  • 30,615
  • 9
  • 51
  • 60
  • marking this as accepted because it suggested a library function (even if not in the standard load), and didn't roll its own (which really wasn't what I was asking for). – rampion Apr 30 '10 at 14:59
2

There is no standard C library function for this.

When you use the declaration

char example[] = "first line\nsecond line: \"inner quotes\"";

the escape sequences will be interpreted and replaced by the compiler. You will have to "un-interpret" the characters that C escapes. Here's a quick-n-dirty example:

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

void print_unescaped(char* ptr, int len) {
    if (!ptr) return;
    for (int i = 0; i < len; i++, ptr++) {
        switch (*ptr) {
            case '\0': printf("\\0");  break;
            case '\a': printf("\\a");  break;
            case '\b': printf("\\b");  break;
            case '\f': printf("\\f");  break;
            case '\n': printf("\\n");  break;
            case '\r': printf("\\r");  break;
            case '\t': printf("\\t");  break;
            case '\v': printf("\\v");  break;
            case '\\': printf("\\\\"); break;
            case '\?': printf("\\\?"); break;
            case '\'': printf("\\\'"); break;
            case '\"': printf("\\\""); break;
            default:
                if (isprint(*ptr)) printf("%c",     *ptr);
                else               printf("\\%03o", *ptr);
        }
    }
}
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
bta
  • 43,959
  • 6
  • 69
  • 99
  • 1
    I'm not sure if you're aware, but `puts` appends a trailing newline. – dreamlax Apr 27 '10 at 22:23
  • what the hell is `else` for a `switch`? – Michael Krelin - hacker Apr 27 '10 at 22:45
  • Ha! That takes me back to VB days with `Select Case` and `Case Else`. – dreamlax Apr 27 '10 at 23:00
  • @dreamlax: Good point, I should have noted that in my answer. I used `gets` because it was cleaner to read than two `putchar` calls (given the space provided) and since I was trying to communicate the concept more than the implementation. I will edit my answer to clarify this. @Michael Krelin: It's latin for 'default', I translated it back though. Thanks for catching that. – bta Apr 27 '10 at 23:51
1

You just mentioned that you wanted to print the string.

char example[] = "first line\nsecond line: \"inner quotes\"";
size_t length = strlen(example);
size_t i;

static const char *simple = "\\\'\"";
static const char *complex = "\a\b\f\n\r\t\v";
static const char *complexMap = "abfnrtv";

for (i = 0; i < length; i++)
{
    char *p;
    if (strchr(simple, example[i]))
    {
        putchar('\\');
        putchar(example[i]);
    }
    else if ((p = strchr(complex, example[i]))
    {
        size_t idx = p - complex;
        putchar('\\');
        putchar(complexMap[idx]);
    }
    else if (isprint(example[i]))
    {
        putchar(example[i]);
    }
    else
    {
        printf("\\%03o", example[i]);
    }
}
u_Ltd.
  • 544
  • 1
  • 4
  • 9
dreamlax
  • 93,976
  • 29
  • 161
  • 209
1
#include <string.h>    
/* int c_quote(const char* src, char* dest, int maxlen)
 *
 * Quotes the string given so that it will be parseable by a c compiler.
 * Return the number of chars copied to the resulting string (including any nulls)
 *
 * if dest is NULL, no copying is performed, but the number of chars required to 
 * copy will be returned.
 *
 * maxlen characters are copied. If maxlen is negative, 
 * strlen is used to find the length of the source string, and the whole string
 * including the NULL-terminator is copied.
 *
 * Note that this function will not null-terminate the string in dest.
 * If the string in src is not null-terminated, or maxlen is specified to not
 * include the whole src, remember to null-terminate dest afterwards.
 *
 */
int c_quote(const char* src, char* dest, int maxlen) {
    int count = 0;
    if(maxlen < 0) {
        maxlen = strlen(src)+1;      /* add 1 for NULL-terminator */
    }

    while(src  && maxlen > 0) {
        switch(*src) {

            /* these normal, printable chars just need a slash appended */
            case '\\':
            case '\"':
            case '\'':
                if(dest) {
                    *dest++ = '\\';
                    *dest++ = *src;
                }
                count += 2;
                break; 

            /* newlines/tabs and unprintable characters need a special code.
             * Use the macro CASE_CHAR defined below.
             * The first arg for the macro is the char to compare to,
             * the 2nd arg is the char to put in the result string, after the '\' */
#define CASE_CHAR(c, d) case c:\
    if(dest) {\
        *dest++ = '\\'; *dest++ = (d);\
        }\
count += 2;\
break;
            /* --------------  */
            CASE_CHAR('\n', 'n');
            CASE_CHAR('\t', 't');
            CASE_CHAR('\b', 'b');
            /*  ------------- */

#undef CASE_CHAR


            /* by default, just copy the char over */
            default:
                if(dest) {
                    *dest++ = *src;
                }
                count++;
        }

        ++src;
        --maxlen;
    }
    return count;
}
gnud
  • 77,584
  • 5
  • 64
  • 78
0

No standard C function, but not too hard to roll your own

nothing too pretty but :-

void escape_str(char *dest, char *src)
{
     *dest = 0;
     while(*src)
     {
         switch(*src)
         {
             case '\n' : strcat(dest++, "\\n"); break;
             case '\"' : strcat(dest++, "\\\""); break;
             default:  *dest = *src;
         }
         *src++;
         *dest++;
         *dest = 0;                     
     }
}
Keith Nicholas
  • 43,549
  • 15
  • 93
  • 156
0

I felt like my previous answer was cheating because a function that writes to a buffer is much more useful than one that simply writes to stdout, so here's an alternative solution which works out how much memory one needs if dst is NULL, and also stops at dstLen as per the requirement.

It's probably a little inefficient with all the if(dst) checking.

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

size_t str_escape(char *dst, const char *src, size_t dstLen)
{
    const char complexCharMap[] = "abtnvfr";

    size_t i;
    size_t srcLen = strlen(src);    
    size_t dstIdx = 0;

    // If caller wants to determine required length (supplying NULL for dst)
    // then we set dstLen to SIZE_MAX and pretend the buffer is the largest
    // possible, but we never write to it. Caller can also provide dstLen
    // as 0 if no limit is wanted.
    if (dst == NULL || dstLen == 0) dstLen = SIZE_MAX;

    for (i = 0; i < srcLen && dstIdx < dstLen; i++)
    {
        size_t complexIdx = 0;

        switch (src[i])
        {
            case '\'':
            case '\"':
            case '\\':
                if (dst && dstIdx <= dstLen - 2)
                {
                    dst[dstIdx++] = '\\';
                    dst[dstIdx++] = src[i];
                }
                else dstIdx += 2;
                break;

            case '\r': complexIdx++;
            case '\f': complexIdx++;
            case '\v': complexIdx++;
            case '\n': complexIdx++;
            case '\t': complexIdx++;
            case '\b': complexIdx++;
            case '\a':
                if (dst && dstIdx <= dstLen - 2)
                {
                    dst[dstIdx++] = '\\';
                    dst[dstIdx++] = complexCharMap[complexIdx];
                }
                else dstIdx += 2;
                break;

            default:
                if (isprint(src[i]))
                {
                    // simply copy the character
                    if (dst)
                        dst[dstIdx++] = src[i];
                    else
                        dstIdx++;
                }
                else
                {
                    // produce octal escape sequence
                    if (dst && dstIdx <= dstLen - 4)
                    {
                        dst[dstIdx++] = '\\';
                        dst[dstIdx++] = ((src[i] & 0300) >> 6) + '0';
                        dst[dstIdx++] = ((src[i] & 0070) >> 3) + '0';
                        dst[dstIdx++] = ((src[i] & 0007) >> 0) + '0';
                    }
                    else
                    {
                        dstIdx += 4;
                    }
                }
        }
    }

    if (dst && dstIdx <= dstLen)
        dst[dstIdx] = '\0';

    return dstIdx;
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
-3
while(*src++)
{
  if(*src == '\\' || *src == '\"' || *src == '\'')
    *dest++ = '\\';

  *dest++ = *src++;
}
Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67