2

I am currently printing a string using printf("'%.*s'\n", length, start);, where start is a const char* and length is an int.

The string being printed sometimes contains newline characters \n which mess up the formatting, is it possible for them to be replaced with the characters \ and n in the printed output, instead of the \n character. If not, could you help with a function that would replace the characters?

The string is a malloc'd string that is has other references from different pointers so it cannot be changed.

EDIT: I have written the following code which I think does what I need it to

static void printStr(const char* start, int length) {
  char* buffer = malloc(length * sizeof(char));
  int processedCount = 0;
  for(int i = 0; i < length; i++) {
    char c = start[i];
    if(c == '\n') {
      printf("%.*s\\n", processedCount, buffer);
      processedCount = 0;
    } else {
      buffer[processedCount] = c;
      processedCount++;
    }
  }
  printf("%.*s", processedCount, buffer);
  free(buffer);
}
Computer Backup
  • 187
  • 1
  • 3
  • 14
  • 4
    Yes, it is possible, but not with `printf` but by preprocessing the string before printing it. – Eugene Sh. Jun 14 '18 at 20:27
  • @EugeneSh. I have added some code to preprocess the string, are there any issues with it? – Computer Backup Jun 14 '18 at 20:43
  • 1
    If you have an answer to your question, use the "Answer Your Question" button below. Don't edit your question to include the answer, because this is confusing to whoever will read it in future. – anatolyg Jun 14 '18 at 21:45
  • 1
    If you want to avoid all non-printable characters, some [sample code](https://stackoverflow.com/a/22152332/2410359). – chux - Reinstate Monica Jun 14 '18 at 21:51
  • Usually if one wants to `'\n'` --> `"\n"`, then one needs to also escape a `'\'` and then `'\'` --> `"\\"` to distinguish the cases of a string of `backslash` and `n` versus `\n`. – chux - Reinstate Monica Jun 14 '18 at 21:52

4 Answers4

3

There is no need to allocate memory to process the string. Simply, iterate through the original one and print the characters as required. For instance:

#include <stdio.h>

void print(const char * str, int length)
{
    for (int i = 0; i < length; ++i) {
        if (str[i] == '\n') {
            putchar('\\');
            putchar('n');
        } else
            putchar(str[i]);
    }
}

int main()
{
    print("hello\nworld!", 12);
    return 0;
}
Acorn
  • 24,970
  • 5
  • 40
  • 69
2

I would implement your custom print function slightly differently:

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

static int output_escaped(FILE *out, const char *str, const char *end)
{
    int  count = 0;
    while (str < end)
        switch (*str) {
        case '\\': fputs("\\\\", out); count++; break;
        case '\a': fputs("\\a", out);  count++; break;
        case '\b': fputs("\\b", out);  count++; break;
        case '\t': fputs("\\t", out);  count++; break;
        case '\n': fputs("\\n", out);  count++; break;
        case '\v': fputs("\\v", out);  count++; break;
        case '\f': fputs("\\f", out);  count++; break;
        case '\r': fputs("\\r", out);  count++; break;
        default:
            if (isprint((unsigned char)(*str)))
                fputc(*str, out);
            else {
                fprintf(out, "\\x%02x", (unsigned char)(*str));
                count += 3; /* Note: incremented by one later */
            }
        }
        str++;
        count++;
    }
    return count;
}

with wrapper functions

int escape(const char *str)
{
    if (!stdout || !str) {
        errno = EINVAL;
        return -1;
    } else
        return output_escaped(stdout, str, str + strlen(str));
}

int escapen(const char *str, const size_t len)
{
    if (!stdout || !str) {
        errno = EINVAL;
        return -1;
    } else
        return output_escaped(stdout, str, str + len);
}

int fescape(FILE *out, const char *str)
{
    if (!out || !str) {
        errno = EINVAL;
        return -1;
    } else
        return output_escaped(out, str, str + strlen(str));
}

int fescapen(FILE *out, const char *str, const size_t len)
{
    if (!out || !str) {
        errno = EINVAL;
        return -1;
    } else
        return output_escaped(out, str, str + len);
}

The four wrappers cater for the cases when you want to print the entire thing, or just some len first characters, to stdout or a specific stream. All return the number of characters output, or -1 if an error occurs (and errno set).

Note that this prints \ as \\, and other non-printable characters using a hexadecimal escape sequence \x00 to \xFF. If you want octal (\001 through \377) rather than hexadecimal escapes, use

            else {
                fprintf(out, "\\%03o", (unsigned char)(*str));
                count += 3; /* Note: incremented by one later */
            }

instead.

The (unsigned char) casts ensure that the character value is never negative.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • @chux: Good point; thanks! Now fixed. I always misremember the octal escape rules; the sole exception, `\0`, throws me off. (It makes me think single-octal digit codes are all expressed that way, but they are not. All except zero are three octal digits long.) – Nominal Animal Jun 14 '18 at 22:05
  • 1
    @chux: Yes, I did a nice headpalm when I edited the code and added the comment, and realized that too. :) And then I realized I missed the first one, and did another. Now my forehead is red. Serves me right for giving a bad example.. But thank you, again, for catching the errors! I really appreciate it. – Nominal Animal Jun 14 '18 at 22:10
  • You should not pass `char` values to `isprint()`. It is only defined for arguments values in the range of the `uni=signed char` type and the value `EOF`. An easy fix is `isprint((unsigned char)*str)` – chqrlie Jun 14 '18 at 22:13
  • There are functions to classify characters, like `isanum()` ( https://linux.die.net/man/3/isspace ). Id use that instead of the clumsy `switch()`... – Michael Beer Jun 14 '18 at 22:18
  • @chqrlie: How did I miss that? My forehead is becoming sore ... Anyway, thanks for pointing that out; now fixed! In fact, I do believe I've been bitten by that at some point years and years ago, when using an ISO 8859-1/15 locale and a localized program doing `isprint()` or similar checks. – Nominal Animal Jun 14 '18 at 22:27
  • @MichaelBeer: There is no function that will tell you when a backspace-letter escape sequence is available: you could only use hexadecimal or octal escapes, then. I happen to think `\n` is much more informative than `\x0A` or `\012` for most users, so the use of switch() was very deliberate. Note that I do use `isprint()` to determine if a character needs escaping at all. Because \ is a printable character, doing the `isprint()` test first would lead to even messier code, I think. – Nominal Animal Jun 14 '18 at 22:32
0

This is essentially the same thing, but it uses strchr in memory to see exactly how many chars we can write before an escape sequence, and fwrites them all in one. This may be faster then character-at-a-time output, (perhaps related https://stackoverflow.com/a/37991361/2472827.) (Not thoroughly tested.)

#include <stdlib.h> /* EXIT_ */
#include <stdio.h>  /* perror fwrite fputs */
#include <string.h> /* strchr */
#include <assert.h>

/** Prints the string, escaping newlines to "\n"; a newline will be added at
 the end.
 @return A non-negative number, otherwise EOF; in the latter case, it shall
 set an error indicator for the stream and (may) set {errno} (IEEE Std
 1003.1-2001-conforming systems?) */
static int printStr(const char* start) {
    const char *so_far = start, *nl;
    size_t nchars;
    assert(start);
    while((nl = strchr(so_far, '\n'))) {
        nchars = nl - so_far;
        if(fwrite(so_far, 1, nchars, stdout) != nchars
            || fwrite("\\n", 1, 2, stdout) != 2) return EOF;
        so_far = nl + 1;
    }
    /* The rest. */
    return fputs(so_far, stdout) == EOF || fputc('\n', stdout) == EOF ? EOF : 1;
}

int main(void) {
    return printStr("Lalala\n\nlalala\n\n") == EOF
        ? perror("string"), EXIT_FAILURE : EXIT_SUCCESS;
}

It doesn't require length, but you could put in by checking nchars, (if your string was not null-terminated?)

Neil
  • 1,767
  • 2
  • 16
  • 22
0

The code you posted in your question seems to work, except for the potential undefined behavior if memory allocation fails. It can be simplified: there is no need for a temporary buffer, you can print from the supplied array directly, avoiding allocation:

static void printStr(const char *s, int length) {
    int i, j;
    for (i = j = 0; i < length; i++) {
        if (s[i] == '\n') {
            printf("%.*s\\n", i - j, s + j);
        }
    }
    if (i > j) {
        printf("%.*s", i - j, s + j);
    }
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189