Question

Is there an easy way to escape all special characters in the printf() function?

The reason why I would like to know how to do this is because I am printing a number of characters which may include special characters such as the null character (\0) and the beep character and I just want to see the contents of the string.

Currently I am using the following code

It works for null characters. What would be the easiest way to escape all special characters?

int length;
char* data = GetData( length ); // Fills the length as reference

for( int i = 0;  i < length;  i++ )
{
    char c = data[ i ];
    printf( "%c", ( c == 0  ?  '\\0'  :  data[ i ] ) );
}
Was it helpful?

Solution

First of all, '\\0' is a two-character literal, which should really be a two-character string. As for printing all special characters as escape code, you need some more code:

switch (data[i])
{
case '\0':
    printf("\\0");
    break;
case '\n':
    printf("\\n");
    break;

/* Etc. */

default:
    /* Now comes the "hard" part, because not all characters here
     * are actually printable
     */
    if (isprint(data[i]))
        printf("%c", data[i]);  /* Printable character, print it as usual */
    else
        printf("\\x%02x", data[i]); /* Non-printable character, print as hex value */

    break;
}

OTHER TIPS

Use the isprint library function to determine if the character is printable:

#include <ctype.h>
...
if (isprint(data[i]))
  printf(" %c", data[i]);    // prints character
else
  printf(" %d", data[i]);    // prints code value for character

In case code needs to write with no ambiguity, using C syntax:

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

void EscapePrint(int ch) {
  // Delete or adjust these 2 arrays per code's goals
  // All simple-escape-sequence C11 6.4.4.4
  static const char *escapev = "\a\b\t\n\v\f\r\"\'\?\\";
  static const char *escapec = "abtnvfr\"\'\?\\";
  char *p = strchr(escapev, ch);
  if (p && *p) {
    printf("\\%c", escapec[p - escapev]);
  } else if (isprint(ch)) {
    fputc(ch, stdout);
  } else {
    // Use octal as hex is problematic reading back
    printf("\\%03o", ch);
  }
}

void EscapePrints(const char *data, int length) {
  while (length-- > 0) {
    EscapePrint((unsigned char) *data++);
  }
}

Alternatively, code could

void EscapePrint(char sch) {
  int ch = (unsigned char) sch;
  ...
}
void EscapePrints(const char *data, int length) {
  while (length-- > 0) {
    EscapePrint(*data++);
  }
}

To use a hexadecimal-escape-sequence or a shorten octal-escape-sequence, code needs to insure that the next character does not create ambiguity. That complication does not occur in the above code as it uses 3-digit octal-escape-sequences. Amended code would be something like:

  } else {
    if ((ch == 0) && (nextch < '0' || nextch > '7')) {
      fputs("\\0", stdout);
    }
    else if (!isxdigit((unsigned char) nextch)) {
      printf("\\x%X", ch);
    }
    else {
      // Use octal as hex is problematic reading back
      printf("\\%03o", ch);
    }
  }
#include <stdio.h>
#include <ctype.h>


/* Converts a buffer of specified lenth to
 * ASCII representation as it was a C string literal.
 * Returns how much bytes from source was processed
 * (ideally ret == src_sz)
 */
int binbuf_to_escaped_C_literal(const char *src_buf, size_t src_sz, char *dst_str, size_t dst_sz)
{
    const char *src = src_buf;
    char *dst = dst_str;

    while (src < src_buf + src_sz)
    {
        if (*src == '\\')
        {
            *dst++ = '\\';
            *dst++ = *src++;
        }
        else if (isprint(*src))
        {
            *dst++ = *src++;
        }
        else
        {
            switch(*src)
            {
                case '\n':
                    *dst++ = '\\';
                    *dst++ = 'n';
                    break;
                case '\r':
                    *dst++ = '\\';
                    *dst++ = 'r';
                    break;
                case '\t':
                    *dst++ = '\\';
                    *dst++ = 't';
                    break;
                case '\0':
                    *dst++ = '\\';
                    *dst++ = '0';
                    break;
                default:
                    sprintf(dst, "0x%x", *src);
                    dst += 4;
            }
            src++;
        }

        // next iteration requires up to 5 chars in dst buffer, for ex.  "0xab\0"
        if (dst > (dst_str + dst_sz - 5)) {
            break;
        }
    }

    *dst = '\0';

    return src - src_buf;
}


int main(int argc, char **argv)
{
    const char binbuf[] = "strange \n\r\t\0\0\0\0\0\\\\ string";
    size_t sz = sizeof(binbuf) - 1; // drop trailing nul terminator

    char escaped[128];

    if (binbuf_to_escaped_C_literal(binbuf, sz, escaped, sizeof(escaped)) != sz) {
        fprintf(stderr, "Destination string buffer is too small\n");
        return 1;
    }

    printf("Escaped: %s\n", escaped);

    // $ ./escape-binary-buf                                          //
    // Escaped: strange \n\r\t\0\0\0\0\0\\\\ string                   //

    return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top