Kris
Kris

Reputation: 95

How to print text with formatting

My problem is when I try to print text with a "\n", this special characters are invisible for printf and puts after echoing it to file, and reading it again.

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

int main()
{   
    FILE *f;
    char *s = (char*) malloc (2919);
    strcpy(s, "printf 'H4sIAIM4aFYCAwtJLS6JyQsBklwAMrDLnwsAAAA=' | base64 -d | gunzip > r"); //Test\nTest after decoding
    system(s);
    f = fopen("r", "r");
    fseek(f, SEEK_SET, 0);
    fread(s, 2919, 1, f);
    printf("%s", s); //puts(s); gives the same result
    fclose(f);
    system("rm r");
    free(s);
    return 0;
}

Output should look like:

Test
Test

and it looks like Test\nTest. What am I doing wrong? Learning purpose, so please be nice.

Upvotes: 4

Views: 145

Answers (5)

chqrlie
chqrlie

Reputation: 145317

Your code has the following problems:

  • fseek(f, SEEK_SET, 0); serves no purpose, the file opened with fopen is at position 0 by default.

  • fread(s, 2919, 1, f);: you do not store the number of bytes read. You cannot properly null terminate the buffer for printf to stop at the last decoded byte. How do you know the file size anyway?

  • The encoded string H4sIAIM4aFYCAwtJLS6JyQsBklwAMrDLnwsAAAA= decodes as Test\nTest, with a litteral \ character followed by an n, not a linefeed character. Reading these characters from file r with fread will not convert escape sequence \n to the actual linefeed character. Such conversion is a feature of the compiler when parsing string and character literals. If you intend to do that with your file contents, you have to hand code the conversion yourself.

Here is a corrected version:

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

int main(void) {   
    FILE *f;
    char *s = malloc(2919 + 1);
    char *p;
    int nread;

    strcpy(s, "printf 'H4sIAIM4aFYCAwtJLS6JyQsBklwAMrDLnwsAAAA=' | base64 -d | gunzip > r"); //Test\nTest after decoding
    system(s);
    f = fopen("r", "r");
    nread = fread(s, 2919, 1, f);
    if (nread >= 0) {
        s[nread] = '\0';
        while ((p = strstr(s, "\\n")) != NULL) {
            /* converting \ n sequences to linefeed characters */
            *p = '\n';
            memmove(p + 1, p + 2, strlen(p + 2) + 1);
        }
        printf("%s", s); //puts(s); will not give the same result
    }
    fclose(f);
    system("rm r");
    free(s);
    return 0;
}

Upvotes: 3

A.S.H
A.S.H

Reputation: 29362

The C compiler transforms the "\n" sequence into a newline character (ASCII code 10) ONLY IF it is encountered in a literal character string (a constant presented in the source code), not in any character string variable.

Examples:

char s1[] = "TEST\nTEST";
printf(s1); // ---> TEST newline TEST.

char s2[] = "TEST\\nTEST"; // s2 = "TEST\nTEST"
printf(s2); // ---> TEST\nTEST (the characters \ and n are present inside the string)

As you can see, in the case of s1, the string was first parsed by the C compiler, which then transformed \n into newline. In the second case, the escape character \ prevented the interpretation so the string s2 was exactly TEST\nTEST, but now this is no longer a literal (given explicitly in the code), but a real character string residing in memory. Hence it will no longer be interpreted.

Therefore, printf(s2) is not like printf("TEST\nTEST") because in the former case there is no interpretation, while in the latter case, the C compiler sees a literal and will interpret it, replacing \n by newline.

Upvotes: 2

wally
wally

Reputation: 3592

@lurker has hit the nail on the head...

The encoding evidently has a literal backslash and nothing in your pipeline of system commands interprets it. One way to get it to interpret the escapes is: "echo -e $(printf 'H4sIAIM4aFYCAwtJLS6JyQsBklwAMrDLnwsAAAA=' | base64 -d | gunzip) r"

To be a little more verbose in answering the question - specifically what's happened is you've compressed the literal ASCII (or UTF-8) characters:

T  e  s  t  \  n  T  e  s  t

Given your question, you were (presumably) meaning to have either compressed a new line character (often interpreted by tools from the string \n) rather than the two literal characters \ and n.

You could create a different gzipped input. That's one option. I don't know how you generated the current gzipped binary data - if you post that, we can take a look and propose a fix.

Alternatively as @lurker said - you can do something on the output to convert any occurrences of \ and n to newline characters - and there's lots of ways to do that.

But it really depends on what you were trying to do. If you thought you had zipped this text:

Test
Test

...then it's the input that's gone wrong.

If you thought you had zipped this text:

Test\nTest

...(as in literally a back-slash-and-n), then you presumably are trying to add some output-processing to convert it to a real new line character.

Does that make sense?

Upvotes: 2

glglgl
glglgl

Reputation: 91159

Well, obviously, you have encoded the bytes '\' and 'n' at the respective places in the string.

If you don't want that, you can

  1. either compress and encode the correct string:

    $ echo $'Test\nTest' | gzip | base64
    H4sIAEs+aFYAAwtJLS7hCgERAF0muOIKAAAA
    $ echo $'Test\nTest' | gzip -n | base64
    H4sIAAAAAAAAAwtJLS7hCgERAF0muOIKAAAA
    
  2. or somehow interpret the \n in the string as wanted. But this makes everything more complicated.

Upvotes: 2

dbush
dbush

Reputation: 225817

The text you've encoded looks like this:

Test\nTest

This is a 10 character string with "\" for the fifth character and "n" for the sixth. This is different from this:

char str[]="Test\nTest";

Which is a 9 character string with a newline for the fifth character.

If you want to print a newline, your encoded string needs to contain it. Either that, or you have to parse the resulting string and perform the newline substitution manually.

Upvotes: 4

Related Questions