I've this basic code that I use from time to time. Not heavily tested, does the job for me
const size_t maxCLSize = 60;
const size_t maxParameters = 10;
// Crude parser
// we recognize the form: command [option]
// option can be either label=value or just label
// eg accelerate speed=100 distance=33 noCrash
// once parsed we will have
//Â Â instruction.parsingStatus = PARSING_OKÂ Â Â Â Â Â --> the parsing was fine
//Â Â instruction.count = 3Â Â Â Â Â Â Â Â Â Â Â Â Â Â --> number of parameters
//Â Â instruction.command = "accelerate"Â Â Â Â Â Â Â Â --> what to do
//Â Â instruction.parameters[] = {{"speed", "100"}, {"distance","33"}, {"noCrash", NULL}, {NULL, NULL} ...}
//
// we do not handle quotes in the command such as --> print "Hello World"
// that will lead to
//Â Â instruction.parsingStatus = PARSING_OK
//Â Â instruction.count = 2
//Â Â instruction.command = "print"
//Â Â instruction.parameters[] = {{"\"Hello", NULL}, {"World\"",NULL"}, {NULL, NULL} ...}
struct CLItem_t {
 const char* label;
 const char* value;
};
enum parsingStatus_t : byte {PARSING_OK, PARSING_COMMAND_EMPTY, PARSING_COMMAND_TOO_LONG};
struct cli_t {
 parsingStatus_t parsingStatus;
 size_t count;
 const char * command;
 CLItem_t parameters[maxParameters];
} instruction;
boolean splitCommand(char * c_line, cli_t& instruction)
{
 if (strlen(c_line) == 0) {
  instruction.parsingStatus = PARSING_COMMAND_EMPTY;
  return false; // error, empty command
 }
 char * splitPtr;
 size_t index = 0;
 splitPtr = strtok(c_line, " ");
 instruction.parsingStatus = PARSING_OK;
 instruction.command = splitPtr;
 instruction.count = 0;
 splitPtr = strtok(NULL, " ");
 while (splitPtr != NULL)
 {
  if (index >= maxParameters) {
   instruction.parsingStatus = PARSING_COMMAND_TOO_LONG;
   break;
  } else {
   char * ptrEqual = strchr(splitPtr, '=');
   instruction.count++;
   instruction.parameters[index].label = splitPtr;
   if (ptrEqual) {
    *ptrEqual = '\0';
    instruction.parameters[index].value = ptrEqual + 1;
   } else instruction.parameters[index].value = NULL;
   index++;
   splitPtr = strtok(NULL, " ");
  }
 }
 return (instruction.parsingStatus == PARSING_OK);
}
boolean listenStream(Stream& incomingStream, char *c_line, cli_t& instruction)
{
 static byte indexMessage = 0;
 boolean messageReceived = false;
 if (indexMessage == 0) instruction.parsingStatus = PARSING_COMMAND_EMPTY;
 while (incomingStream.available() && !messageReceived) {
  int c = incomingStream.read();
  if (c != -1) { // -1 indicates an error
   switch (c) {
    case '\n':
     c_line[indexMessage] = '\0'; // c-string correct ending
     indexMessage = 0; // get ready for next time
     messageReceived = true;
     break;
    default:
     if (indexMessage <= maxCLSize - 1) {
      if (!isspace(c) || (indexMessage != 0 && !isspace(c_line[indexMessage - 1])))
       c_line[indexMessage++] = (char) c; // store the received character, but get rid of multiple spaces
     } else {
      instruction.parsingStatus = PARSING_COMMAND_TOO_LONG;
     }
     break;
   }
  }
 }
 return messageReceived;
}
boolean checkCommandLine(Stream& incomingStream, cli_t& instruction)
{
 static char commandLine[maxCLSize + 1] = {'\0'};
 boolean newCommand = listenStream(incomingStream, commandLine, instruction);
 if (newCommand && instruction.parsingStatus != PARSING_COMMAND_TOO_LONG) splitCommand(commandLine, instruction);
 return newCommand;
}
void setup() {
 Serial.begin(115200);
}
void parseCommand( cli_t& instruction)
{
 if (instruction.parsingStatus != PARSING_OK) {
  Serial.print(F("Error parsing new command line: "));
  Serial.println(instruction.parsingStatus);
 } else {
  Serial.print(F("\nWe dealt with a new command line -> "));
  Serial.println(instruction.command);
  for (size_t i = 0; i < instruction.count; i++) {
   Serial.print(F("P")); Serial.print(i);
   Serial.print(F("\t")); Serial.print(instruction.parameters[i].label);
   if (instruction.parameters[i].value) {
    Serial.print(F(" <- ")); Serial.print(instruction.parameters[i].value);
   } // end value
   Serial.println();
  } // end for
 }
}
void loop() {
 if (checkCommandLine(Serial, instruction)) parseCommand(instruction);
 // you can do other stuff here
}
(console set @115200 bauds, sending either CR+LF or LF)
if you type in the Serial console [color=purple]accelerate speed=100 distance=33 noCrash[/color] and hit return
you will see in the console the result of the split of the command line:
[color=blue]We dealt with a new command line -> accelerate
P0 speed <- 100
P1 distance <- 33
P2 noCrash
[/color]
if you type in the Serial console [color=purple]Hello World[/color] and hit return
you will see in the console the result of the split of the command line:
[color=blue]We dealt with a new command line -> Hello
P0 World[/color]
This is just a command splitter, you still need to parse the resulting structure.
Note that in order to conserve memory and not use dynamic allocation, there is only one static buffer shared for all instructions, so if you need to remember something across commands, then you need to do that in the parsing stage because next input will mess with the pointers.