Skip to content

Instantly share code, notes, and snippets.

@georgyangelov
Last active December 29, 2015 15:19
Show Gist options
  • Save georgyangelov/7689591 to your computer and use it in GitHub Desktop.
Save georgyangelov/7689591 to your computer and use it in GitHub Desktop.
Simple shell implementation using POSIX system primitives
#include <unistd.h> // For system constants
#include <stdio.h> // For sprintf
#include <string.h> // For strlen and strcmp mainly
#include <stdlib.h> // For exit (the library function) & getenv
#include <stdarg.h> // For the variable arguments function
/* Constants */
#define MAX_LINE_LENGTH 1000
#define MAX_ARG_LENGTH 100
#define MAX_ARGS_COUNT 50
// Used for the current working directory path
#define MAX_PATH_LENGTH 2048
/* Boolean type */
typedef char bool;
#define true 1
#define false 0
/* Return type of exec_command */
typedef char ecmd_t;
#define ECMD_INTERNAL 0
#define ECMD_EXTERNAL 1
#define ECMD_ASYNC 2
/* Function declarations */
void print_string(const char* format, ...);
void print_newline();
char* replace_home(char*);
void print_prompt();
void read_command(char*);
bool is_arg_delimiter(char, char);
char* trim(char*);
void init_buffers();
void reset_args_buffer();
int split_args(char**, const char*);
bool exec_command(int, char**, int* pid_or_status);
void quit();
/* Global variables */
// Used by print_string
char print_buffer[MAX_LINE_LENGTH + 1];
char cwd[MAX_PATH_LENGTH + 1];
char* args_buffer[MAX_ARGS_COUNT + 1];
// Used to hold the pointers to strings as some may be deleted (by the 0-terminator)
char* args_buffer_refs[MAX_ARGS_COUNT + 1];
bool status_printed = true;
ecmd_t cmd_type;
int status;
// Allocates heap memory for some of the buffers
void init_buffers() {
int i;
for (i = 0; i < MAX_ARGS_COUNT; i++) {
args_buffer_refs[i] = (char*)malloc(MAX_ARG_LENGTH);
}
args_buffer_refs[MAX_ARGS_COUNT] = 0;
reset_args_buffer();
}
void reset_args_buffer() {
int i;
for (i = 0; i < MAX_ARGS_COUNT; i++) {
args_buffer_refs[i][0] = 0;
args_buffer[i] = args_buffer_refs[i];
}
args_buffer[MAX_ARGS_COUNT] = 0;
}
void main() {
int i, num_args;
char cmd[MAX_LINE_LENGTH + 1];
init_buffers();
getcwd(cwd, MAX_PATH_LENGTH + 1);
replace_home(cwd);
while (true) {
print_prompt();
read_command(cmd);
trim(cmd);
if (strlen(cmd) == 0) continue;
reset_args_buffer();
num_args = split_args(args_buffer, cmd);
// Make sure it doesn't break on this command: ""
if (num_args == 0) continue;
// for (i = 0; args_buffer[i]; i++) {
// print_string("Argument %d: %s\n", i, args_buffer[i]);
// }
cmd_type = exec_command(num_args, args_buffer, &status);
status_printed = false;
if (cmd_type == ECMD_ASYNC) {
print_string("Started async process %d\n", status);
} else if (status == -1 && strcmp(args_buffer[0], "cd") == 0) {
print_string("Cannot find or open directory\n");
}
}
}
/* Other function definitions */
void print_prompt() {
char* status_string = "";
if (!status_printed && cmd_type != ECMD_ASYNC) {
status_string = status == 0 ? " :)" : " :(";
status_printed = true;
}
print_string("%s %s $ ", status_string, cwd);
}
void read_command(char* cmd) {
char c;
int pos = 0;
while (read(STDIN_FILENO, &c, 1) > 0) {
if (c == '\n') break;
cmd[pos++] = c;
}
cmd[pos] = 0;
}
ecmd_t exec_command(int argc, char** argv, int* status) {
// TODO: Support logic control: ;, &&, ||
// TODO: Support pipes: |, <, >
ecmd_t type = ECMD_EXTERNAL;
if (strcmp(argv[0], "exit") == 0) {
// Exit the shell
quit();
}
else if (strcmp(argv[0], "cd") == 0) {
// Change the current directory
if (argc == 1) {
*status = chdir(getenv("HOME"));
} else {
*status = chdir(argv[1]);
}
getcwd(cwd, MAX_PATH_LENGTH + 1);
replace_home(cwd);
return ECMD_INTERNAL;
}
if (strcmp(argv[argc - 1], "&") == 0) {
type = ECMD_ASYNC;
argv[argc - 1] = 0;
argc--;
}
int pid = fork();
if (pid) {
if (type != ECMD_ASYNC) {
waitpid(pid, status, 0);
}
else {
*status = pid;
}
return type;
}
else {
execvp(argv[0], argv);
print_string("Unknown command %s\n", argv[0]);
exit(1);
}
}
char* replace_home(char* path) {
char* home = getenv("HOME");
int i, length = strlen(home);
if (length == 0 || strstr(path, home) != path) {
return path;
}
for (i = 0; i < strlen(home); i++) {
path[i] = ' ';
}
path[i - 1] = '~';
trim(path);
return path;
}
int split_args(char** args, const char* cmd) {
int start = 0, length = strlen(cmd);
int current, currentArg = 0;
char scope = ' ';
for (current = 0; current <= length; current++) {
if (scope == ' ' && current == start && is_arg_delimiter(cmd[current], ' ')) {
// Skip multiple whitespaces between the arguments
start++;
}
else if (scope == ' ' && (cmd[current] == '\'' || cmd[current] == '"')) {
scope = cmd[current];
start = current + 1;
}
if (start < current && (is_arg_delimiter(cmd[current], scope) || cmd[current] == 0)) {
// Copy the argument to the args array
strncpy(args[currentArg], cmd + start, current - start);
args[currentArg][current - start] = 0;
scope = ' ';
start = current + 1;
currentArg++;
}
}
args[currentArg] = 0;
return currentArg;
}
void quit() {
print_string("Bye :(\n");
exit(0);
}
bool is_arg_delimiter(char c, char scope) {
if (scope == ' ') {
return c == ' ' || c == '\t';
} else {
return c == scope;
}
}
char* trim(char* str) {
int pos, i, length = strlen(str);
for (pos = 0; str[pos] == ' ' || str[pos] == '\t'; pos++);
if (pos > 0) {
for (i = 0; i < length; i++) {
str[i] = str[i + pos];
}
}
for (pos = strlen(str) - 1; str[pos] == ' ' || str[pos] == '\t'; pos--);
str[pos + 1] = 0;
return str;
}
void print_string(const char* format, ...) {
va_list arguments;
va_start(arguments, format);
vsprintf(print_buffer, format, arguments);
va_end(arguments);
write(STDOUT_FILENO, print_buffer, strlen(print_buffer));
}
void print_newline() {
write(STDOUT_FILENO, "\n", 1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment