C Programming

How to Create a Simple Shell in C?

The shell is like a program that receives command inputs from the user’s keyboard and sends them to a machine to be executed by the kernel. It also checks to see if the user’s command inputs are correct. It could be a command-line interface, like the one we’ll create, or a graphical user interface, like normal software such as Microsoft Office or Adobe Suite.

This tutorial will guide you through the stages of creating an independent simple shell in C. After completing this tutorial, you should have a better understanding of the various processes and functions involved, as well as a clear workable way to code by yourself.

What is the Basic Lifetime of the Shell?

During its lifespan, a shell accomplishes three major tasks.

  • Initialize: In this stage, a typical shell will read as well as execute its set of configuration files. These alter the shell’s behavior.
  • Interpret: The shell then reads commands from “stdin” and executes them.
  • Terminate: After the execution of its commands, the shell performs any of the shutdown commands, frees any memory, and terminates.

These stages are general and they may be applicable to a wide range of programs, but we will use them as the foundation for our shell. Our shell will be so basic that there will be no configuration files and no shutdown command. So, we will simply execute the looping function and then exit. However, it’s essential to remember that the program’s lifetime is more than just looping.

How to Create a Simple Shell in C?

We will create a basic shell in C that will demonstrate the fundamentals of how it functions. Because its goal is demonstration rather than feature completeness or even fitness for casual use, it has a number of limitations, including

  • All commands must be typed in one line.
  • Whitespace must be utilized for separating arguments.
  • There will be no quoting or escaping whitespace.
  • There is no piping or reroute.
  • The only built-ins are ‘cd’, ‘help’, and ‘exit’.

Now have a look at a C program that is building a simple shell.

#include <sys/wait.h>

#include <sys/types.h>

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int komal_cd(char **args);

int komal_help(char **args);

int komal_exit(char **args);

char *built_in_string[] =

{

  "cd",

  "help",

  "exit"

};

int (*built_in_function[]) (char **) =

{

&komal_cd,

&komal_help,

&komal_exit

};

int komal_builtins()

{

  return sizeof(built_in_string) / sizeof(char *);

}

  int komal_cd(char **args)

{

  if (args[1] == NULL)

{

fprintf(stderr, "komal: expected argument to "cd"\n");

  }

else

{

  if (chdir(args[1]) != 0)

{

perror("komal");

   }

  }

  return 1;

}

int komal_help(char **args)

{

  int i;

printf("This is simple C shell build by Komal Batool\n");

printf("Type program names and arguments, and hit enter.\n");

printf("The following are built in:\n");

  for (i = 0; i < komal_builtins(); i++)

{

printf(" %s\n", built_in_string[i]);

}

printf("Use the man command for information on other programs.\n");

return 1;

}

int komal_exit(char **args)

{

return 0;

}

int komal_launch(char **args)

{

pid_t pid;

  int status;

pid = fork();

  if (pid == 0)

{

  if (execvp(args[0], args) == -1)

{

perror("komal");

  }

exit(EXIT_FAILURE);
 
  } else if (pid < 0)

{

  perror("komal");

}

  else

{

  do

{

waitpid(pid, &status, WUNTRACED);

    } while (!WIFEXITED(status) && !WIFSIGNALED(status));

  }

  return 1;

}

int komal_execute(char **args)

{

  int i;

  if (args[0] == NULL)

{

  return 1;

  }

  for (i = 0; i < komal_builtins(); i++) {

    if (strcmp(args[0], built_in_string[i]) == 0) {

      return (*built_in_function[i])(args);

  }

  }

  return komal_launch(args);

}

  char *komal_read_line(void)

{

#ifdef komal_USE_STD_GETLINE

  char *line = NULL;

ssize_t bufsize = 0;

  if (getline(&line, &bufsize, stdin) == -1)

{

if (feof(stdin))

{

  exit(EXIT_SUCCESS);

  }

else

{

perror("komal: getline\n");

exit(EXIT_FAILURE);

}

  }

  return line;

#else

#define komal_RL_BUFSIZE 1024

  int bufsize = komal_RL_BUFSIZE;

  int position = 0;

  char *buffer = malloc(sizeof(char) * bufsize);

  int c;

  if (!buffer) {

fprintf(stderr, "komal: allocation error\n");

  exit(EXIT_FAILURE);

  }

  while (1)

{

  c = getchar();

  if (c == EOF)

{

exit(EXIT_SUCCESS);

}

else if (c == '\n')

{

  buffer[position] = '\0';

  return buffer;

 } else {

  buffer[position] = c;

  }

  position++;

  if (position >= bufsize)

{

bufsize += komal_RL_BUFSIZE;

  buffer = realloc(buffer, bufsize);

  if (!buffer)

{

fprintf(stderr, "komal: allocation error\n");

exit(EXIT_FAILURE);

   }

  }

}

#endif

}

#define komal_TOK_BUFSIZE 64

#define komal_TOK_DELIM " \t\r\n\a"

char **komal_split_line(char *line)

{

  int bufsize = komal_TOK_BUFSIZE, position = 0;

  char **tokens = malloc(bufsize * sizeof(char*));

  char *token, **tokens_backup;

  if (!tokens)

{

fprintf(stderr, "komal: allocation error\n");

exit(EXIT_FAILURE);

  }

  token = strtok(line, komal_TOK_DELIM);

  while (token != NULL)

{

  tokens[position] = token;

  position++;
 
  if (position >= bufsize)

{

bufsize += komal_TOK_BUFSIZE;

  tokens_backup = tokens;

  tokens = realloc(tokens, bufsize * sizeof(char*));

if (!tokens)

{

free(tokens_backup);

fprintf(stderr, "komal: allocation error\n");

exit(EXIT_FAILURE);

    }

  }

    token = strtok(NULL, komal_TOK_DELIM);

  }

  tokens[position] = NULL;

  return tokens;

}

void komal_loop(void)

{

  char *line;
 
  char **args;

  int status;

  do

{

printf("> ");

  line = komal_read_line();

args = komal_split_line(line);

  status = komal_execute(args);

  free(line);

  free(args);

  } while (status);

}

int main(int argc, char **argv)

{

komal_loop();

  return EXIT_SUCCESS;

}

Code Description

The above code is a simple implementation of a command-line shell written in C. The shell is named “komal”, and it can execute built-in commands such as “cd”, “help”, and “exit”, as well as external commands. The main function of the program is the “komal_loop” function, which loops continuously, reading input from the user via the “komal_read_line” function, splitting the input into individual arguments using the “komal_split_line” function, and executing the command using the “komal_execute” function.

The “komal_execute” function checks if the command is a built-in command, and if so, it executes the corresponding built-in function. If the command is not a built-in command, it executes an external command by forking a child process and calling the “execvp” system call to replace the child process’s memory space with the desired program.

The “komal_cd”, “komal_help”, and “komal_exit” functions are the three built-in functions that can be executed by the user. “komal_cd” changes the current working directory, “komal_help” provides information about the shell and its built-in commands, and “komal_exit” exits the shell.

Output

Conclusion

Building a simple shell in C involves understanding how to parse and execute commands, handle user input and output, and manage processes using system calls like fork and execvp. The process of creating a shell requires a deep understanding of C programming language and the Unix operating system. However, with the help of the steps and example provided in the above guide, one can create a basic shell that can handle user input and execute commands.

About the author

Komal Batool Batool

I am passionate to research technologies and new ideas and that has brought me here to write for the LinuxHint. My major focus is to write on programming languages and computer science related topics.