Trying to implement a history function in C, I know my array size for the history is 100 and i have a max of 9 arguments. I just wanted a little help or advice on where to start! I also know that i need my history initialized before i start parsing my shell commands. What i have so far:

    #define MAX_ARGS 9


        #define HIS_SIZE 100


        typedef struct
        {
          int argument;                             // userCom arguments
          char *arg[MAX_ARGS + 1];                  // userCom arguments array 
          char* history[HIS_SIZE];
          char *input;                          // hold iniut file
          char *output;                        // hold outiut file

         };Command

    printf("\n%s@myshell:%s>", username,cwd); 

    //gets string from userCom line
    fgets(buffer, MAX_COMMAND_SIZE + 1, stdin);

    buffer[strlen(buffer)-1] = 0 ;//set null

    //SETUP HISTORY FUNCTION 


    //parses tokens and looks for delimiters 
    token[tok] = strtok(buffer,whitespace);
    while(token[tok])
    {
        ++tok;
        if(tok == MAX_ARGS)
        printf("Reached MAX userCom arguments");
        break;

        token[tok] = strtok(NULL, whitespace);
    }

There are a few problems with your code:

  • You don't have a main function
  • The Command structure seems to be keeping the history? Idealy, the history should keep a list of Commands.
  • The if condition in your while needs { and } or you will always break out after the first token.

Have you got a compilable, running example we could examine?

yes i do

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>


#define MAX_COMMAND_SIZE 80
#define MAX_ARGS 9
#define HIS_SIZE 100


typedef struct
{
  int argument;                             // userCom arguments
  char *arg[MAX_ARGS + 1];                  // userCom arguments array 
  char* history[HIS_SIZE];
  char *input;                          // hold iniut file
  char *output;                        // hold outiut file
} Command;





int main()
{
    Command userCom = {0};                       //holds userCom struct
    const char *whitespace = " \n\r\t\v\f";      // userCom delimiting chars
    char* username = getenv("USER");             //Get user name 
    char* curDirect = getenv("HOME");            //get cwd
    char* token[MAX_ARGS];                           
    char* cwd;
    char* buf;
    char* cmd;
    char buffer[MAX_COMMAND_SIZE + 1];       //hold userCom line
    int tok = 0;
    int new;
    int i;
    int limit;
    int last = 0; 
    int hist = 0;                           //initialize history size to 0 
    long size;
    struct stat buff;                       //holds file information


    //gets current working directory and also changes buffer if necessary 
    size = pathconf(".", _PC_PATH_MAX);
    if ((buf = (char *)malloc((size_t)size)) != NULL)
    cwd = getcwd(buf, (size_t)size);

    while(1){

    //prints users prompt 
    printf("\n%s@myshell:%s>", username,cwd); 

    //gets string from userCom line
    fgets(buffer, MAX_COMMAND_SIZE + 1, stdin);

    buffer[strlen(buffer)-1] = 0 ;//set null

    //set-up history function 
     userCom.history[last% HIS_SIZE] = cmd;
     last++;

    //parses tokens and looks for delimiters 
    token[tok] = strtok(buffer,whitespace);
    while(token[tok])
    {
        ++tok;
        if(tok == MAX_ARGS)
        printf("Reached MAX userCom arguments");
        break;

        token[tok] = strtok(NULL, whitespace);
    }

    i =0;
    //sort tokens based on special characters
    for (;i<tok;++i)

    {
        if(!strcmp(token[i], "<"))
            {
             userCom.output = token[++i];
            }
        else if(!strcmp(token[i], ">"))
        {
            userCom.input = token[++i];
        }
        else if (token[i][0] == '$')
        {
          char* toktok = getenv((const char*)&token[i][1]);

          if (!toktok)
            {
              printf("%s: ERROR: variable.\n", token[i]);
              return 0;
            }
          else
            {
              userCom.arg[userCom.argument] = toktok;
              ++(userCom.argument);
            }
        }

      else
        {
          userCom.arg[userCom.argument] = token[i];
          ++(userCom.argument);

        }
    }
        tok = 0;
        userCom.arg[userCom.argument] = 0;  


    //handles the "cd" command
        if((strcmp(userCom.arg[0],"cd") == 0))
        {   
            if (userCom.argument > 2)
          printf("cd: Too many arguments\n");

          // change directories if valid target and update cwd

          else if (userCom.argument == 1)
        {
          new = chdir(cwd);
          if (new != 0)
              printf("%s: No such file or directory\n");

          // if no argument is given, new directory should be $HOME

              else
            {
              new = chdir(curDirect);

              // get the new current working directory

              size = pathconf(".", _PC_PATH_MAX);
              if ((buf = (char *)malloc((size_t)size)) != NULL)
                cwd = getcwd(buf, (size_t)size);
            }
        }   
      }//end "cd" function

      //handles the echo command
      else if(strcmp(userCom.arg[0], "echo") == 0)
      {
        int p; 
        for(p=1;p < userCom.argument; ++p)
        printf("%s ", userCom.arg[p]);      

      }//ends echo function 

      //handles exit 
      else if(strcmp(userCom.arg[0], "exit") == 0)
      {
        exit(0); 
      }
      //handles history functions
      else if(strcmp(userCom.arg[0], "history") == 0)
      {
        int j;
        for(j = last,  limit = 0; userCom.history[j] != NULL && limit != HIS_SIZE ; j = (j -1)% HIS_SIZE, limit++)
        printf(" %s ",userCom.history[j]);
      }
      else {

      }

      }//end while 1 loop

    return 0; 
}//End int main

As L7Sqr has said, your command structure should not contain the command history. The command history should be a separate entity and should maintain a list of commands entered by the user.

WRT the implementation of the history:
I think you'll need to implement a FIFO stack/queue (from now on I'll refer to it as a queue) with space for 100 command strings. Each time a command is entered by the user, push the command string onto the back of the queue. If the queue is full and you want to add another command to the history, simply pop the oldest item off the front before pushing the newest command string onto the back.

Then the history command itself just needs to list the strings stored in the history queue.

I imagine you've probably already covered implementing stacks/queues in your classes/lectures. If not, there are tons of examples on the web to learn from!

Also I have another thought:
When using the Bash shell on a Linux system, the command history is stored in a hidden text file in the users home directory called ".bash_history". I think most other popular shells do the same sort of thing.

Bearing that in mind:
Rather than only storing the previous command history in memory (so the history is effectively only saved for the current session), would it be worth considering storing the history in a file somewhere in the users home directory? e.g. ".yourshellsname_history"

That way your shell will behave more like other linux/unix style shells and the users command history will persist across different shell sessions.
Again, was just a thought! :)

If you choose to use a history file:
When the shell starts, initialise your history queue to be empty. Then check for the presence of your history file in the users home directory. If a history file is found, you read each line in the file into your history queue.

After initialisation, each time a command is entered by the user and a copy of the command-string is pushed onto the history queue, you simply rewrite the history file with the contents of the queue (or if you don't want to write to the file each time the user enters a command, you'll need to use some other mechanism to periodically rewrite the history file).

My two cents! :)

Edited 2 Years Ago by JasonHippy

Compiling your code with warnings cranked up yields:

t.c: In function ‘main’:
t.c:23:5: error: missing initializer [-Werror=missing-field-nitializers]
t.c:23:5: error: (near initialization for ‘userCom.arg’) [-Werror=missing-field-initializers]
t.c:107:15: error: format ‘%s’ expects a matching ‘char *’ argument [-Werror=format]
t.c:39:17: error: unused variable ‘buff’ [-Werror=unused-variable]
t.c:37:9: error: unused variable ‘hist’ [-Werror=unused-variable]
cc1: all warnings being treated as errors

Fixing that you end up with a leak @ if ((buf = (char *)malloc((size_t)size)) != NULL)
You never free that buffer.

Again, during you rtokenizing step (within the while (token[tok])) you only ever read a single token. The code should look like:

    while(token[tok]) 
    {
        ++tok;
        if(tok == MAX_ARGS) {
            printf("Reached MAX userCom arguments");
            break;
        }
        token[tok] = strtok(NULL, whitespace);
    }

Notice the brackets around the if conditional.

This article has been dead for over six months. Start a new discussion instead.