43

For a project I'm trying to read an int and a string from a string. The only problem is sscanf() appears to break reading an %s when it sees a space. Is there anyway to get around this limitation? Here's an example of what I'm trying to do:

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

int main(int argc, char** argv) {
    int age;
    char* buffer;
    buffer = malloc(200 * sizeof(char));
    sscanf("19 cool kid", "%d %s", &age, buffer);

    printf("%s is %d years old\n", buffer, age);
    return 0;
}

What it prints is: cool is 19 years old where I need cool kid is 19 years old. Does anyone know how to fix this?

pevik
  • 4,523
  • 3
  • 33
  • 44
SDLFunTimes
  • 745
  • 2
  • 7
  • 13

5 Answers5

56

The following line will start reading a number (%d) followed by anything different from tabs or newlines (%[^\t\n]).

sscanf("19 cool kid", "%d %[^\t\n]", &age, buffer);
BrunoLM
  • 97,872
  • 84
  • 296
  • 452
  • Thanks. Also for some reason %d stopped working so I used %[0-9]. Thanks again. – SDLFunTimes May 18 '10 at 05:34
  • 1
    But.. but this is disastrously incorrect! `%[0-9]` specifier (and similar ones) can only be used to read strings. And you are reading an `int` (`age`) with it. This cannot work and will not work. – AnT stands with Russia Sep 24 '10 at 02:11
  • If you want to read an `int`, you need `%d` (or maybe `%i` or anything int-compatible). But not `%[]`. – AnT stands with Russia Sep 24 '10 at 02:15
  • Interesting how "will not work" things work. My answer was using `%d` and I changed because of the OP's comment. – BrunoLM Sep 24 '10 at 02:29
  • 1
    You mean that you read an `int` variable with an `%[0-9]` format and then print it as in OP's code and it "works"? Sorry, I have hard time believing that :) No, it doesn't work. I can imagine that you can squeeze a 3-char (or less) string into a 4-byte `int`, but once you print that `int` you'll get garbage. Of course, saying that this accidental hack "works" is an insult to the word "work" :) – AnT stands with Russia Sep 24 '10 at 02:46
  • @AndreyT You sadden me, find happiness elsewhere please – RyanS Jun 19 '12 at 02:10
  • 4
    @RyanS: No, it is not me, it is the natural dissonance between string and numbers that saddens you. They are not the same. It saddens me too at times, but somehow I learned to cope. Why oh why, cruel world?!!! – AnT stands with Russia Jun 19 '12 at 21:44
14

You want the %c conversion specifier, which just reads a sequence of characters without special handling for whitespace.

Note that you need to fill the buffer with zeroes first, because the %c specifier doesn't write a nul-terminator. You also need to specify the number of characters to read (otherwise it defaults to only 1):

memset(buffer, 0, 200);
sscanf("19 cool kid", "%d %199c", &age, buffer);
caf
  • 233,326
  • 40
  • 323
  • 462
  • 13
    Well, that's not going to work for my cousin, who is suspiciously named "George Fortescue Aloicious Broomhilda Doreen Beelzebub ... Johanssen MacGregor" :-) – paxdiablo May 18 '10 at 04:17
  • 7
    Indeed - your cousin should complain to the OP for only allocating a buffer of 200 bytes ;) – caf May 18 '10 at 04:23
  • Good point and, since you've caught the buffer overflow problem with mine and Bruno's answers, +1. Come, join us :-) – paxdiablo May 18 '10 at 05:13
  • 1
    @paxdiablo Well I found this `%199c` useful to me because in my case, the string is actually fixed-length. – Wesley May 27 '15 at 09:26
12

If you want to scan to the end of the string (stripping out a newline if there), just use:

char *x = "19 cool kid";
sscanf (x, "%d %[^\n]", &age, buffer);

That's because %s only matches non-whitespace characters and will stop on the first whitespace it finds. The %[^\n] format specifier will match every character that's not (because of ^) in the selection given (which is a newline). In other words, it will match any other character.


Keep in mind that you should have allocated enough space in your buffer to take the string since you cannot be sure how much will be read (a good reason to stay away from scanf/fscanf unless you use specific field widths).

You could do that with:

char *x = "19 cool kid";
char *buffer = malloc (strlen (x) + 1);
sscanf (x, "%d %[^\n]", &age, buffer);

(you don't need * sizeof(char) since that's always 1 by definition).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 4
    So over here, your cousin causes a crash instead of suffering the indignity of having his name truncated? Also, my cousin, who is even more suspiciously named (with the 3rd character of his second name being a newline) is unhappy. – caf May 18 '10 at 04:23
  • 1
    The comment needs to be read in the context of the comments to my answer. – caf May 18 '10 at 04:39
2

Since you want the trailing string from the input, you can use %n (number of characters consumed thus far) to get the position at which the trailing string starts. This avoids memory copies and buffer sizing issues, but comes at the cost that you may need to do them explicitly if you wanted a copy.

const char *input = "19  cool kid";
int age;
int nameStart = 0;
sscanf(input, "%d %n", &age, &nameStart);
printf("%s is %d years old\n", input + nameStart, age);

outputs:

cool kid is 19 years old
ɲeuroburɳ
  • 6,990
  • 3
  • 24
  • 22
  • Good catch @chux. I've updated the answer to initialize `nameStart = 0`. That will at least prevent a segfault on bad input. – ɲeuroburɳ Nov 09 '15 at 15:13
-1

I guess this is what you want, it does exactly what you specified.

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

int main(int argc, char** argv) {
    int age;
    char* buffer;
    buffer = malloc(200 * sizeof(char));
    sscanf("19 cool kid", "%d cool %s", &age, buffer);
    printf("cool %s is %d years old\n", buffer, age);
    return 0;
}

The format expects: first a number (and puts it at where &age points to), then whitespace (zero or more), then the literal string "cool", then whitespace (zero or more) again, and then finally a string (and put that at whatever buffer points to). You forgot the "cool" part in your format string, so the format then just assumes that is the string you were wanting to assign to buffer. But you don't want to assign that string, only skip it.

Alternative, you could also have a format string like: "%d %s %s", but then you must assign another buffer for it (with a different name), and print it as: "%s %s is %d years old\n".

Rob Heusdens
  • 155
  • 5
  • Dangerous: buffer isn't allocated to point to anything. You'd want to say "char* buffer = malloc(100)" to get memory for it, if 100 bytes will suffice. You'd then need to specify a size for the %s that matches it (99 in this case, leaving room for a 0-terminator which sscanf() will append). – Swiss Frank May 27 '18 at 16:57