Fishingfon
Fishingfon

Reputation: 1034

How to get input of variable chars/strings in C?

I have a problem where I need to get input from the user (command line), and it will either be in the format [char char char] or [char string], ie 3 chars or a char and a string.

I need the chars and string on their own with no white space etc, and it all has to be input on one line.

My current solution only works for 3 chars, but I am unsure of how to get it to work for both 3 chars or a char and a string.

This is my current code:

char move[3];

while(1){
    int i = scanf(" %c %c %c", &move[0], &move[1], &move[2]);

    if(i == 3){
        break;
    }
}       

If someone knows of how I can achieve what I want, I would be very grateful.

Upvotes: 0

Views: 245

Answers (2)

John Bode
John Bode

Reputation: 123468

You could use a state machine to analyze and parse your input. You define some number of states you can be in at any point while reading the input, and the transitions between states based on the input. For example:

  • expect_move1 - you haven't read any input yet, you're expecting to see a single non-whitespace character followed by whitespace;
  • expect_move2_or_string - you've read your first move, and the next thing you expect to see is either another single character followed by whitespace *or* a sequence of non-whitespace characters followed by whitespace;
  • expect_move3 - you've read a second single non-whitespace character followed by whitespace, and the next thing you expect to see is third single non-whitespace character followed by whitespace;
  • done - you have either read 3 single non-whitespace characters *or* you have read 1 single non-whitespace character followed by a sequence of non-whitespace characters, and the only thing that should be left is whitespace;
  • error - you read something you didn't expect, and the input is improperly formatted.

I womped up somewhat quick-n-dirty implementation, which handles various inputs as follows:

[fbgo448@n9dvap997]~/prototypes/state: ./state
a
..^
Bad input found at position 3
a b
....^
Bad input found at position 5
a b c
Read moves: a b c
a b c d
......^
Bad input found at position 7
a string
Read move: a string
a string c
.........^
Bad input found at position 10
a stringthatstoolongforourstringbuffer
......................^
Bad input found at position 23
some string
.^
Bad input found at position 2
a string followed by some junk
.........^
Bad input found at position 10

For your purposes, this is probably the definition of overkill and more involved than you need; consider a badly-written example of how you might want to handle stuff like this in the future:

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

/**
 *   State            Input                   New State
 *   -----            -----                   ---------
 *   exp_mv1          char + whitespace       exp_mv2_or_str
 *   exp_mv2_or_str   char + whitespace       exp_mv3
 *   exp_mv2_or_str   char seq + whitespace   done
 *   done             whitespace              done
 *
 *  Inputs that do not match the above lead to the error state.
 *
 */
int getInput( const char *input,     // input string
              size_t inputSize,      // length of input string
              char *moves,           // array to store moves
              size_t movesLen,       // length of moves array
              char *str,             // array to store string element
              size_t strLen,         // max length of string element array
              size_t *movesRead,     // number of moves read so far
              int *count)            // number of characters processed
{
  enum state { exp_mv1,              // expect a single char + whitespace
               exp_mv2_or_str,       // expect char + whitespace or string
               exp_mv3,              // expect char + whitespace
               done,                 // expect only whitespace until newline
               error }               // found something unexpected
    curState = exp_mv1;              // start in exp_mv1
  *count = 0;

  while ( *count < inputSize &&       // process input while we are not 
           curState != error &&       // at the end of the string and
           input[*count] &&           // we aren't in the error state
           input[*count] != '\n' )
  {
    switch( curState )
    {
      case exp_mv1:
        if ( !isspace( input[*count] ) )        // non-whitespace
        {
          if ( isspace( input[++(*count)] ) )   // followed by whitespace
          {
            moves[0] = input[((*count)++)-1];   // save the move
            (*movesRead)++;                     // update moves counter
            curState = exp_mv2_or_str;          // go to new state
          }
          else
          {
            curState = error;                   // bad input
          }
        }
        else
          (*count)++;                           // read whitespace, move
        break;                                  // to next character

      case exp_mv2_or_str:
        if ( !isspace( input[*count] ) )        // non-whitespace character
        {
          if ( isspace( input[++(*count)] ) )   // followed by whitespace
          {
            moves[1] = input[((*count)++)-1];   // save the move
            (*movesRead)++;                     // update moves counter
            curState = exp_mv3;                 // go to new state
          }
          else
          {
            size_t i = 0;                       // non-whitespace
            --(*count);                         // back up one character
            while ( i < strLen &&               // read characters until we
                    !isspace( input[*count] ) ) // run out of space or hit
            {                                   // a whitespace character
              str[i++] = input[(*count)++];     // and save to str
            }
            str[i] = 0;                         // 0-terminate str 

            if ( isspace( input[*count] ) )     // if current character is
              curState = done;                  // not whitespace, then the
            else                                // string was longer than
            {                                   // what we can store
              (*count)--;                       // previous character was first bad character
              curState = error;                 // go to error state
            }
          }
        }
        else
          (*count)++;                           // skip over whitespace character
        break;

      case exp_mv3:
        if ( !isspace( input[*count] ) )        // non-whitespace
        {
          if ( isspace( input[++(*count)] ) )   // followed by whitespace
          {
            moves[2] = input[((*count)++)-1];   // save the move
            (*movesRead)++;                     // update the moves counter
            curState = done;                    // go do done state
          }
          else
          {
            --count;                            // non-whitespace, previous char is bad character
            curState = error;                   // go to error state         
          }
        }
        else
          (*count)++;                           // skip whitespace character
        break;

      case done:
        if ( !isspace( input[*count] ) )        // non-whitespace character
        {
          curState = error;                     // go to error state
        }
        else
          (*count)++;                           // skip whitespace character

        break;

      case error:                               // no processing,
        break;
    }
  }
  return curState == done;
}

int main( void )
{
   char input[81];
   char moves[3];
   char str[21];
   size_t movesRead;
   int charsRead;

   while( fgets( input, sizeof input, stdin ) )
   {
     movesRead = 0;
     if ( getInput( input, strlen( input ), moves, sizeof moves, str, sizeof str, &movesRead, &charsRead ) )
     {
       if ( movesRead == 3 )
         printf( "Read moves: %c %c %c\n", moves[0], moves[1], moves[2] );
       else
         printf( "Read move: %c %s\n", moves[0], str );
     }
     else
     {
       const char *leader = ".....................................................................";

       fprintf( stderr, "%*.*s^\n", charsRead, charsRead, leader );
       fprintf( stderr, "Bad input found at position %d\n", charsRead+1 );
     }
  }

  return 0;
}

I apologize for posting a novel - you caught me at a weird time.

Upvotes: -1

Angus Comber
Angus Comber

Reputation: 9708

A common way is to use something like this:

char move[80];
char* tok = NULL;
if (fgets(move, 80, stdin) != NULL) {
    printf("Now you need to parse: %s\n", move);
    /* then split into tokens using strtok */
    tok = strtok(move, " "); 
    while (tok != NULL)
    {
        printf("Element: %s\n", tok);
        tok = strtok(NULL, " ");
    }
}

And then parse move using the C string handling functions. You can use strtok to get your 2+ tokens. If each element has strlen 1 then that is your 3 characters case and if first character is strlen 1 and second is 1+ length that is your second case.

Upvotes: 2

Related Questions