Last active
December 29, 2015 15:19
-
-
Save georgyangelov/7689591 to your computer and use it in GitHub Desktop.
Simple shell implementation using POSIX system primitives
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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