25

Command processor

  1. typedef list shell;
A type shell is a list of commands.
  1. extern shell shell_new( void );
  2. extern void shell_free( shell this );
  3. extern shell shell_help( shell this, char *input );
  4. extern void shell_inputline( shell this, char *input_line );
  5. extern void shell_input( shell this, char *prompt );
  6. extern shell shell_addcmd( shell this, char *name, char *help, void (*f)( int argc, char **argv ) );
  7. extern shell shell_delcmd( shell this, char *name );

shell_new returns a new instance of the type shell.
shell_free deallocates the memory space occupied by a shell.
shell_help displays a summary of all the available commands.
shell_inputline interprets a line of text and runs the corresponding function.
shell_input reads a line of text, passes it to shell_inputline and displays a message prompt.
shell_addcmd adds a command to the processor.
shell_delcmd removes a command from the processor.

  1. typedef struct _shell_keyword {
  2.     char *name, *help;
  3.     void (*f)( int argc, char **argv );
  4. } *shell_keyword;

The type shell_keyword defines a command. name defines its name, how it's called. help is the command usage which is displayed by shell_help. f is a pointer to the function called to run the command.

  1. } *shell_keyword;
  2.  
  3. static shell_keyword shell_keyword_alloc( void ) {
  4.     return (shell_keyword)calloc( 1, sizeof ( struct _shell_keyword ));
  5. }
  6.  
  7. static shell_keyword shell_keyword_init( shell_keyword this, char *name, char *help, void (*f)( int argc, char **argv ) ) {
  8.     this->name = name;
  9.     this->help = help;
  10.     this->f = f;
  11.  
  12.     return this;
  13. }
  14.  
  15. static shell_keyword shell_keyword_new( char *name, char *help, void (*f)( int argc, char **argv ) ) {
  16.     return shell_keyword_init( shell_keyword_alloc(), name, help, f );
  17. }
  18.  
  19. static void shell_keyword_free( shell_keyword this ) {
  20.     free( this );
  21. }
  1. shell shell_alloc( void ) {
  2.     return list_alloc();
  3. }
  4.  
  5. shell shell_init( shell this ) {
  6.     return this;
  7. }
  8.  
  9. shell shell_new( void ) {
  10.     return shell_init( shell_alloc() );
  11. }
  12.  
  13. void shell_free( shell this ) {
  14.     int i, len = list_length( this );
  15.  
  16.     for ( i = 0; i < len; i++ )
  17.         shell_keyword_free( list_get( this, i ));
  18.  
  19.     list_free( this );
  20. }
  1. shell shell_help( shell this, char *input ) {
  2.     shell_keyword kw;
  3.     int i, len = list_length( this );
  4.    
  5.     unsigned lin = input ? strlen( input ) : 0;
  6.  
  7.     for ( i = 0; i < len; i++ ) {
  8.         kw = (shell_keyword)list_get( this, i );
  9.         if ( !lin || strncmp( kw->name, input, lin ) == 0 )
  10.             fprintf( stderr, "%s\n", kw->help );
  11.     }
  12.     return this;
  13. }
  1. static shell_keyword shell_match( shell this, char *input ) {
  2.     shell_keyword kw, found = 0;
  3.     int i, len = list_length( this );
  4.  
  5.     unsigned lin = input ? strlen( input ) : 0;
  6.  
  7.     for ( i = 0; i < len; i++ ) {
  8.         kw = (shell_keyword)list_get( this, i );
  9.         if ( strncmp( kw->name, input, lin ) == 0 ) {
  10.             if ( !found )
  11.                 found = kw;
  12.             else { /* ambiguous input */
  13.                 shell_help( this, input );
  14.                 return 0 ;
  15.             }
  16.         }
  17.     }
  18.     if ( !found )
  19.         shell_help( this, 0 ); /* help user */
  20.  
  21.     return found;
  22. }
  1. static int parse( char *s, char ***argv ) {
  2.     register char *p;
  3.  
  4.     int argc;
  5.     int i;
  6.  
  7.     /* count tokens */
  8.     for ( argc = 0, p = s; *p; ) {
  9.         while ( isspace( *p ))
  10.             p++;    /* skip spaces between tokens */
  11.  
  12.         if ( *p == '\0' )
  13.             break;  /* end of line */
  14.  
  15.         argc++; /* one more token */
  16.  
  17.         while ( *p && !isspace( *p ))
  18.             p++;    /* goto to end of token */
  19.     }
  20.  
  21.     /* return 0 if empty */
  22.     if ( argc == 0 )
  23.         return 0;
  24.  
  25.     /* allocate space for array of arg pointers */
  26.     *argv = (char **)calloc( argc + 1, sizeof( char * ));
  27.  
  28.     /* fill array of pointers */
  29.     for ( i = 0, p = s; *p; ) {
  30.         while ( isspace( *p ))
  31.             p++;    /* skip spaces between tokens */
  32.  
  33.         if ( *p == '\0' )
  34.             break;
  35.  
  36.         (*argv)[ i++ ] = p; /* put token address in argv */
  37.  
  38.         while ( *p && !isspace( *p ))
  39.             p++;    /* go to end of token */
  40.  
  41.         if ( *p )
  42.             *p++ = '\0';
  43.     }
  44.  
  45.     return argc;
  46. }
  1. void shell_inputline( shell this, char *input_line ) {
  2.     shell_keyword kw;
  3.  
  4.     int argc;
  5.     char **argv;
  6.  
  7.     if ( (argc = parse( input_line, &argv )) ) {
  8.         if ( (kw = shell_match( this, argv[ 0 ] ))) {
  9.             argv[ 0 ] = kw->name;   /* full name */
  10.             (kw->f)( argc, argv );
  11.         }
  12.         free( argv );
  13.     }
  14. }
  1. void shell_input( shell this, char *prompt ) {
  2.     char *input_line;
  3.  
  4.     if ( (input_line = readline( prompt )) )  {
  5.         shell_inputline( this, input_line );
  6.         free( input_line );
  7.     }
  8. }
  1. void shell_input( shell this, char *prompt ) {
  2.     char input_line[ 4096 ];
  3.  
  4.     if ( fgets( input_line, sizeof ( input_line ), stdin )) {
  5.         shell_inputline( this, input_line );
  6.  
  7.         if ( prompt ) {
  8.             fprintf( stdout, "%s", prompt );
  9.             fflush( stdout );
  10.         }
  11.     }
  12. }
  1. shell shell_addcmd( shell this, char *name, char *help, void (*f)( int argc, char **argv ) ) {
  2.     list_put( this, -1, shell_keyword_new( name, help, f ));
  3.     return this;
  4. }
  5.  
  6. shell shell_delcmd( shell this, char *name ) {
  7.     shell_keyword kw;
  8.     int i, len = list_length( this );
  9.  
  10.     for ( i = 0; i < len; i++ ) {
  11.         kw = (shell_keyword)list_get( this, i );
  12.         if ( strcmp( kw->name, name ) == 0 ) {
  13.             list_delete( this, i );
  14.             break;
  15.         }
  16.     }
  17.     return this;
  18. }
  1. #if defined( STANDALONE )
  2.  
  3. #include <time.h>
  4.  
  5. struct _app {
  6.     shell sh;
  7. } app;
  8.  
  9. static void cmd_echo( int argc, char **argv ) {
  10.     while ( --argc )
  11.         printf( argc > 1 ? "%s " : "%s\n", *++argv );
  12. }
  13.  
  14. static void cmd_date( int argc, char **argv ) {
  15.     time_t clock = time( (time_t *)0 );
  16.  
  17.     fprintf( stdout, "%s", ctime( &clock ));
  18. }
  19.  
  20. static void cmd_time( int argc, char **argv ) {
  21.     time_t clock = time( (time_t *)0 );
  22.     struct tm *tm = localtime( &clock );
  23.  
  24.     fprintf( stdout, "%02d:%02d:%02d\n", tm->tm_hour, tm->tm_min, tm->tm_sec );
  25. }
  26.  
  27. static void cmd_quit( int argc, char **argv ) {
  28.     shell_free( app.sh );
  29.     exit( 0 );
  30. }
  31.  
  32. int main( void ) {
  33.     char *prompt = "? ";
  34.  
  35.     app.sh = shell_new( );
  36.  
  37.     shell_addcmd( app.sh, "echo",   "echo word...", cmd_echo );
  38.     shell_addcmd( app.sh, "date",   "date",         cmd_date );
  39.     shell_addcmd( app.sh, "time",   "time",         cmd_time );
  40.     shell_addcmd( app.sh, "quit",   "quit | exit",  cmd_quit );
  41.     shell_addcmd( app.sh, "exit",   "exit | quit",  cmd_quit );
  42.  
  43.     setbuf( stdout, 0 );    /* interactive */
  44. #ifndef READLINE
  45.     fprintf( stdout, "%s", prompt );
  46. #endif
  47.     for ( ;; )  /* exit with a command */
  48.         shell_input( app.sh, prompt );
  49. }
  50.  
  51. #endif

Install the libreadline library if necessary:

$ apt-get install libreadline-dev

NOTE: If you don't want to use readline, delete the line which defines READLINE in the file shell.c and don't add the option -lreadline when linking a program with the toolkit.

To build the test program, compile list.c separately then shell.c with -DSTANDALONE:

$ gcc -Wall -c list.o
$ gcc -Wall -DDEBUG -DSTANDALONE -c shell.c -o test-shell.o
$ gcc test-shell.o list.o -o test-shell -lreadline

Try the program:

$ ./test-shell
?

Ask for help:

? ?
echo word...
date
time
quit | exit
exit | quit
? 

Display the date and the time:

? date
Wed May 26 13:02:48 2010
? time
13:02:50
? 

Try a command with a variable number of arguments:

? echo Hello my dear Frasq!
Hello my dear Frasq!
? 

Check what happens when the name of a command is ambiguous:

? e
echo word...
exit | quit
? 

Terminate the program:

? q
$ 

Comments

Your comment:
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip help 2000

Enter a maximum of 2000 characters.
Improve the presentation of your text with the following formatting tags:
[p]paragraph[/p], [b]bold[/b], [i]italics[/i], [u]underline[/u], [s]strike[/s], [quote]citation[/quote], [pre]as is[/pre], [br]line break,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]command[/code], [code=language]source code in c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].