1

Making a very basic shell given as assignment. Assuming I have a string "ls a b c | grep a"

How can I get something like this

commands[0][0] = "ls"
commands[0][2] = "a"
commands[0][2] = "b"
commands[0][3] = "c"
commands[0][4] = NULL

commands[1][0] = "grep"
commands[1][1] = "a"
commands[1][2] = NULL

First split on "|" and then split further on spaces and hence be able to execute execvp(commands[0][0], commands[0])

I can do single splitting just fine but this double splitting is somewhat troublesome

Mat
  • 202,337
  • 40
  • 393
  • 406
rockstarjindal
  • 293
  • 1
  • 5
  • 15
  • This is very closely related to [Strange strtok problem](http://stackoverflow.com/questions/11911884/strange-strtok-behaviour). The problem there was splitting first on `;` instead of `|`, but involved double loops of tokenization. The addtional wrinkle there was to preserve the original string unbutchered (since the `strtok` family of functions modify their input string). One additional observation; once you get to the point of supporting quoted string arguments, any technique based on `strtok` ceases to be appropriate. However, that is for the future, not an immediate problem. – Jonathan Leffler Aug 19 '12 at 15:25

2 Answers2

3

I have not tested with execvp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define WHITESPACE 1
#define ARGUMENT 2
#define COMMAND 3
void process_command(const char *string)
{
   char *argument = (char *) 0;
   char *args[100];
   char *const * commands[10];
   int commands_index = 0;
   int char_index = 0;
   int args_index = 0;
   int state = WHITESPACE;
   commands[commands_index] = &args[args_index];
   for (const char *p = string; *p != '\0'; p++)
   {
      if ( *p != ' ' )
      {
         if ( *p == '|' )
         {
            if ( state == ARGUMENT)
            {
               argument[char_index] = '\0';
               char_index = 0;
            }
            state = COMMAND;
            args[args_index] = 0;
            args_index++;
            commands_index++;
            commands[commands_index] = &args[args_index];
         }
         else
         {
            if ( state != ARGUMENT )
            {
               argument = malloc(100);
               args[args_index] = argument;
               args_index++;
               state = ARGUMENT;
            }
            argument[char_index] = *p;
            char_index++;
         }
      }
      else 
      {
         if ( state == ARGUMENT)
         {
            argument[char_index] = '\0';
            char_index = 0;
         }
         state = WHITESPACE;
      }
   }
   argument[char_index] = '\0';
   args[args_index] = 0;
   //execvp(commands[0][0],commands[0]);
   //execvp(commands[1][0],commands[1]);

   for (int i = 0; i <= commands_index; i++)
   {
      int j = 0;
      for (; commands[i][j] != 0;j++)
      {
         printf("commands[%d][%d] = \"%s\"\n",i,j,commands[i][j]);
      }
      printf("commands[%d][%d] = NULL\n",i,j);
   }
   printf("\n");
}



int main(void)
{

   const char *string1 = "ls a b c | grep a";
   const char *string2 = "ls -al|grep txt";
   const char *string3 = "cat file.txt | grep hello |more";
   process_command(string1);
   process_command(string2);
   process_command(string3);
}

output:

commands[0][0] = "ls"
commands[0][1] = "a"
commands[0][2] = "b"
commands[0][3] = "c"
commands[0][4] = NULL
commands[1][0] = "grep"
commands[1][1] = "a"
commands[1][2] = NULL

commands[0][0] = "ls"
commands[0][1] = "-al"
commands[0][2] = NULL
commands[1][0] = "grep"
commands[1][1] = "txt"
commands[1][2] = NULL

commands[0][0] = "cat"
commands[0][1] = "file.txt"
commands[0][2] = NULL
commands[1][0] = "grep"
commands[1][1] = "hello"
commands[1][2] = NULL
commands[2][0] = "more"
commands[2][1] = NULL
Scooter
  • 6,802
  • 8
  • 41
  • 64
  • +1: This solution is adaptable to single-quoted string arguments and double-quoted string arguments, and could handle `&`, `;`, `||` and `&&` command separators as easily as `|`. – Jonathan Leffler Aug 19 '12 at 15:50
1

You can try using strtok_r to split the string and get tokens and build the expected command line/array of string.

Rohan
  • 52,392
  • 12
  • 90
  • 87
  • strtok_r accepts argument of type *char where as on the first split I would be requiring **char – rockstarjindal Aug 19 '12 at 12:36
  • @rockstarjindal that is to store position in the string you can pass address of `char *`. Look at example in the man page of `strtok_r` – Rohan Aug 19 '12 at 12:41
  • 1
    @jacekmigacz my assignment specifically says to take an input, parse fork and execute. – rockstarjindal Aug 19 '12 at 13:09
  • @Rohan I still don't understand how will I pass the results of `strtok_r` to `execvp` since the latter accepts a whole array instead of a single subtoken.Will copying work?? – rockstarjindal Aug 19 '12 at 13:13