0

I wanna know why sscanf is storing the \n in the date buffer, how can I avoid it in order to have an output like this? : YYYY_MM_DD1234

    char yyyy[10];
    char mm[10];
    char dd[10];
    char date[100];

    puts("Give the date YYYY/MM/DD");
    fgets(date, sizeof date, stdin);
    sscanf(date, "%10[^_]_%10[^_]_%10[^_]", yyyy, mm, dd);

    strcat(date, "1234");
    printf("%c", date);

This is the output given by the program:

Output : YYYY_MM_DD
1234
petrados
  • 55
  • 7
  • But you can also check out this question https://stackoverflow.com/questions/5240789/scanf-leaves-the-new-line-char-in-the-buffer. – Kevin Ng Dec 07 '19 at 02:51
  • You should be using %s while printing a string. I don't think sscanf is the culprit, comment out the sscanf and check again, you'll get the same result. – PunyCode Dec 07 '19 at 02:51
  • So the problem comes from fgets func? – petrados Dec 07 '19 at 02:58
  • I agree to @amintou that is true, I just noticed that. :) your date is from fgets() and not scanf() :). – Kevin Ng Dec 07 '19 at 03:05
  • 1
    Change the final `%10[^_]` to `%10[^\n]` All *line-oriented* input functions (`fgets()` and POSIX `getline()`) read and include the `'\n'` in the buffer they fill. Your `%10[^_]` includes the `'\n'` following `DD` input in `dd`. In actually as you request the format your call should be `sscanf(date, "%10[^/]/%10[^/]/%10[^\n]", yyyy, mm, dd);` to separate your requested input into `yyyy`, `mm`, and `dd`. Then use `sprintf` fill another buffer with your desired format. – David C. Rankin Dec 07 '19 at 04:12
  • 2
    Did you mean `printf("%s", date);` instead of `printf("%c", date);`? – Spikatrix Dec 07 '19 at 04:23
  • @DavidRankin-ReinstateMonica - look carefully, the OP used fgets() then concat that string from fgets() to something else, but the OP does not want the end line. So it has nothing to do with any of the OP's scanf statement. – Kevin Ng Dec 07 '19 at 04:52
  • 1
    @KevinNg you less than diplomatic comment is incorrect. `fgets()` includes the `'\n'` following `DD` (e.g. `"..DD\n"`). The `sscanf` call fails to remove that `'\n'` using the `[...]` format specifier containing the inverted character `'_'` (which simply reads past the `'\n'` until the `'\0'` is encountered including the `\n` in `dd`). So what part of that is wrong? – David C. Rankin Dec 07 '19 at 04:52
  • @DavidRankin-ReinstateMonica - No look at the question carefully. The scanf can't fail to remove that, it does not even store any end line to begin with. Try run a scanf statement and keep on pressing enter and see what happen? Thank you. – Kevin Ng Dec 07 '19 at 04:54
  • In that sense you are correct -- I was assuming the `strcat(date, "1234");` and printf ("%c", date)` was wrong to begin with because that doesn't even use the separated value. – David C. Rankin Dec 07 '19 at 04:55
  • @DavidRankin-ReinstateMonica - I think you just like me, after sometimes not programming in c I forgot that scanf does not parse end line into the data. :). Thank you. – Kevin Ng Dec 07 '19 at 04:55
  • Yes, but the `%[...]` conversion specifier will simply ignore *whitespace* too `:)` – David C. Rankin Dec 07 '19 at 04:57
  • @DavidRankin-ReinstateMonica - No, it just that when fgets() read the data, it read the end line. When the OP concat that thing with strcat(), the "1234" is concat to the date array(str), with the end line in the middle of them and the OP did not want that. – Kevin Ng Dec 07 '19 at 04:57
  • 1
    Yes, yes, the `sscanf` call in the question does not even match the requested input. – David C. Rankin Dec 07 '19 at 04:59
  • :D, I know, the OP forgot that the OP used fgets() or unless the OP wanted to troll us. – Kevin Ng Dec 07 '19 at 05:00
  • Maybe even a bit of [Cargo cult programming](https://en.wikipedia.org/wiki/Cargo_cult_programming) `:)` – David C. Rankin Dec 07 '19 at 05:01
  • Please read the MAN page for `fgets()` Amongst other details you will read that `fgets()` inputs any '\n' into the input buffer. You can (reliably) eliminate the (possible) trailing '\n' by using: `date[ strspn( date, "\n" ) ] = '\0';` – user3629249 Dec 08 '19 at 16:32

4 Answers4

2

If I understand what you are trying to do, take "YYYY/MM/DD" and rearrange it to "YYYY_MM_DD1234", then continuing from my comment, all you need is to separate your input with sscanf and create a new string with sprintf.

All line-oriented input functions (fgets() and POSIX getline()) read and include the '\n' in the buffer they fill. Your %10[^_] includes the '\n' following DD input in dd. You will need to change your sscanf format string to:

sscanf (date, " %9[^/]/%9[^/]/%9[^\n]", yyyy, mm, dd)

Then you simply write to a new string with:

sprintf (newfmt, "%s_%s_%s1234", yyyy, mm, dd);

A short example would be:

#include <stdio.h>

#define NCHR 10     /* if you need a constant, #define one (or more) */

int main (void) {

    char yyyy[NCHR],
        mm[NCHR],
        dd[NCHR],
        date[NCHR*NCHR],
        newfmt[NCHR*NCHR];

    fputs ("Give the date YYYY/MM/DD: ", stdout);
    if (fgets (date, sizeof date, stdin)) {
        if (sscanf (date, " %9[^/]/%9[^/]/%9[^\n]", yyyy, mm, dd) != 3) {
            fputs ("error: invalid date format.\n", stderr);
            return 1;
        }
        sprintf (newfmt, "%s_%s_%s1234", yyyy, mm, dd);
        puts (newfmt);
    }
}

Example Use/Output

$ ./bin/newdatefmt
Give the date YYYY/MM/DD: YYYY/MM/DD
YYYY_MM_DD1234

Look things over and let me know if you have any further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
2

First of all, you need to know that %10[^_] reads 10 characters or everything until a _ character, whichever comes first.

Now, back to your code, fgets(date, sizeof date, stdin); reads in YYYY_MM_DD\n from the user.

Now lets break "%10[^_]_%10[^_]_%10[^_]" down:

  • %10[^_] reads YYYY (and stops as the next character is a _) and stores it in yyyy
  • _ reads and discards the _
  • %10[^_] reads MM (and stops as the next character is a _) and stores it in mm
  • _ reads and discards the _
  • %10[^_] reads DD\n (and stops as it reached the end of the string) and stores it in dd

See the problem?

A simple fix would be to change the final %10[^_] to %10[^\n] so that it scans everything until a newline character as opposed to everthing until a _ character.


Side note: You should be using 9 instead of 10 in the sscanf string as you'd need to reserve one space for the NUL-terminator (\0) marking the end of the string. Also, as @chux says in a comment, it is recommended to check the return value of sscanf to make sure that everything has been parsed successfully. It returns the number of items successfully scanned and assigned. In your case, it it returns 3, we're good to go!

Spikatrix
  • 20,225
  • 7
  • 37
  • 83
  • Note: `"%10[^_]" ... yyyy` read nothing if the first character is `'_'`, the scanning stops and `yyyy` is left uninitialized, Better to check sscanf()` return value. – chux - Reinstate Monica Dec 07 '19 at 05:16
0

The following proposed code:

  1. does not include error checking
  2. performs the desired functionality
  3. cleanly compiles

and now, the proposed code:

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

int main( void )
{
    char date[100];

    puts("Give the date YYYY/MM/DD");

    // -5 to allow room for "1234" which will be added later
    fgets(date, sizeof(date) -5, stdin);

    // remove posssible trailing newline
    date[ strcspn( date, "\n" ) ] = '\0';

    //convert each '/' to '_'
    for( size_t i=0; date[i]; i++ )
    {
        if( date[i] == '/' )
        {
            date[i] = '_';
        }
    }

    // append the desired trailing string
    strcat(date, "1234");

    // display the resulting string on the terminal
    printf("%s\n", date);
}
user3629249
  • 16,402
  • 1
  • 16
  • 17
-2

This code below is to get a string with scanf. You don't have to use fgets() for the code above. fgets() is actually the one that consumes the end line character. Since you probably don't want to go through that string to remove that end line from fgets(). You can directly use scanf and this method below is not that unsafe either. I provide the tester below so you can test it out. Don't use fflush(stdin), that does not always work.

This is the string to look at. Also, thank you to @JonathanLeffler and @chux-ReinstateMonica for pointing it out that I don't the s after %*[^\n].

"%10s%*[^\n]"

It just meant read up to 10 characters into a string. If the user input more than 10 characters then everything else after 10 will be discarded but not the end line. Then right after that, use a blank getchar() to remove that end line.

Also, you used fget() for date and not sscanf. So your problem is not a sscanf problem. sscanf will not store the end line into your buffer. Also, I removed the test code and just left behind your code with a fix. From the old code, I even left fflush(stdin) in there. I was just testing to see if it was working across compilers :). Also, remember your string format needed 10 characters, so your buffer or array should be of length 11.

Although that, remember that data validation is your issue, you did not ask how to validate the date array.

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

int main()
{
    char yyyy[10];
    char mm[10];
    char dd[10];
    char date[100];

    puts("Give the date YYYY/MM/DD");
    // Discard everything except endl
    scanf("%10s%*[^\n]", date); 
    // Get endl out of stdin
    getchar();

    sscanf(date, "%4s%*c%2s%*c%2s", yyyy, mm, dd);

    strcat(date, "1234");
    printf("Date: %s\n", date);
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Kevin Ng
  • 2,146
  • 1
  • 13
  • 18
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/203799/discussion-on-answer-by-kevin-ng-why-sscanf-storing-n-in-a-buffer). – Samuel Liew Dec 07 '19 at 08:31