One way to split the input buffer as OP desires is to use multiple calls to sscanf()
, and to use the %n
conversion specifier to keep track of the number of characters read. In the code below, the input string is scanned in three stages.
First, the pointer strPos
is assigned to point to the first character of inputStr
. Then the input string is scanned with " %c%n%*[^ ]%n"
. This format string skips over any initial whitespaces that a user might enter before the first character, and stores the first character in command
. The %n
directive tells sscanf()
to store the number of characters read so far in the variable n
; then the *[^ ]
directive tells sscanf()
to read and ignore any characters until a whitespace character is encountered. This effectively skips over any remaining characters that were entered after the initial command
character. The %n
directive appears again, and overwrites the previous value with the number of characters read until this point. The reason for using %n
twice is that, if the user enters a character followed by a whitespace (as expected), the second directive will find no matches, and sscanf()
will exit without ever reaching the final %n
directive.
The pointer strPos
is moved to the beginning of the remaining string by adding n
to it, and sscanf()
is called a second time, this time with "%5s%n%*[^ ]%n"
. Here, up to 5 characters are read into the character array firstname[]
, the number of characters read is saved by the %n
directive, any remaining non-whitespace characters are read and ignored, and finally, if the scan made it this far, the number of characters read is saved again.
strPos
is increased by n
again, and the final scan only needs "%s"
to complete the task.
Note that the return value of fgets()
is checked to be sure that it was successful. The call to fgets()
was changed slightly to:
fgets(inputStr, sizeof inputStr, stdin)
The sizeof
operator is used here instead of IN_BUF_SIZE
. This way, if the declaration of inputStr
is changed later, this line of code will still be correct. Note that the sizeof
operator works here because inputStr
is an array, and arrays do not decay to pointers in sizeof
expressions. But, if inputStr
were passed into a function, sizeof
could not be used in this way inside the function, because arrays decay to pointers in most expressions, including function calls. Some, @DavidC.Rankin, prefer constants as OP has used. If this seems confusing, I would suggest sticking with the constant IN_BUF_SIZE
.
Also note that the return values for each of the calls to sscanf()
are checked to be certain that the input matches expectations. For example, if the user enters a command and a first name, but no surname, the program will print an error message and exit. It is worth pointing out that, if the user enters say, a command character and first name only, after the second sscanf()
the match may have failed on \n
, and strPtr
is then incremented to point to the \0
and so is still in bounds. But this relies on the newline being in the string. With no newline, the match might fail on \0
, and then strPtr
would be incremented out of bounds before the next call to sscanf()
. Fortunately, fgets()
retains the newline, unless the input line is larger than the specified size of the buffer. Then there is no \n
, only the \0
terminator. A more robust program would check the input string for \n
, and add one if needed. It would not hurt to increase the size of IN_BUF_SIZE
.
#include <stdio.h>
#include <stdlib.h>
#define IN_BUF_SIZE 256
int main(void)
{
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
char *strPos = inputStr; // next scan location
int n = 0; // holds number of characters read
if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
if (sscanf(strPos, " %c%n%*[^ ]%n", &command, &n, &n) < 1) {
fprintf(stderr, "Input formatting error: command\n");
exit(EXIT_FAILURE);
}
strPos += n;
if (sscanf(strPos, "%5s%n%*[^ ]%n", firstname, &n, &n) < 1) {
fprintf(stderr, "Input formatting error: firstname\n");
exit(EXIT_FAILURE);
}
strPos += n;
if (sscanf(strPos, "%5s", surname) < 1) {
fprintf(stderr, "Input formatting error: surname\n");
exit(EXIT_FAILURE);
}
printf("%c %s %s\n", command, firstname, surname);
}
Sample interaction:
a Zaphod Beeblebrox
a Zapho Beebl
The fscanf()
functions have a reputation for being subtle and error-prone; the format strings used above may seem a little bit tricky. By writing a function to skip to the next word in the input string, the calls to sscanf()
can be simplified. In the code below, skipToNext()
takes a pointer to a string as input; if the first character of the string is a \0
terminator, the pointer is returned unchanged. All initial non-whitespace characters are skipped over, then any whitespace characters are skipped, up to the next non-whitespace character (which may be a \0
). A pointer is returned to this non-whitespace character.
The resulting program is a little bit longer than the previous program, but it may be easier to understand, and it certainly has simpler format strings. This program does differ from the first in that it no longer accepts leading whitespace in the string. If the user enters whitespace before the command
character, this is considered erroneous input.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define IN_BUF_SIZE 256
char * skipToNext(char *);
int main(void)
{
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
char *strPos = inputStr; // next scan location
if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
if (sscanf(strPos, "%c", &command) != 1 || isspace(command)) {
fprintf(stderr, "Input formatting error: command\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", firstname) != 1) {
fprintf(stderr, "Input formatting error: firstname\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", surname) != 1) {
fprintf(stderr, "Input formatting error: surname\n");
exit(EXIT_FAILURE);
}
printf("%c %s %s\n", command, firstname, surname);
}
char * skipToNext(char *c)
{
int inWord = isspace(*c) ? 0 : 1;
if (inWord && *c != '\0') {
while (!isspace(*c)) {
++c;
}
}
inWord = 0;
while (isspace(*c)) {
++c;
}
return c;
}