This project is a simple shell implementation written in C, designed to provide basic shell functionality such as parsing commands, searching for executables in the system’s PATH, handling built-in commands (cd, exit), and executing external programs. The shell provides a controlled environment for process management and command execution.
The Shell Implementation in C provides fundamental features for executing user commands. It includes:
PATH environment variable to locate executables.cd and exit.command structure.cd: Changes the current working directory.exit: Exits the shell.PATH environment variable.new and delete, ensuring no memory leaks or dangling pointers.shell.h: Header file containing definitions, constants, and function prototypes.shell.c: Main implementation of the shell logic.Makefile: File for building the project using make.tests.cpp: Unit tests to verify the shell’s functionality (optional, if included).new/delete).make
./main
To ensure there are no memory leaks:
make valgrind
./main
cd /home/user
or
pwd
ls -l
exit
The parse function takes a user input string, tokenizes it into arguments, and constructs a command structure:
command* parse(char* line) {
// Clone input to avoid modifying original
char* line_copy = new char[strlen(line) + 1];
strcpy(line_copy, line);
// Tokenize input and count arguments
int argc = 0;
char* currentToken = strtok(line_copy, " \t\n");
while (currentToken != NULL) {
argc++;
currentToken = strtok(NULL, " \t\n");
}
// Create command structure
command* cmd = create_command(argc);
if (!cmd) return NULL;
// Populate arguments
strcpy(line_copy, line); // Reset line_copy
currentToken = strtok(line_copy, " \t\n");
for (int i = 0; i < argc && currentToken != NULL; i++) {
strncpy(cmd->argv[i], currentToken, MAX_ARG_LEN - 1);
cmd->argv[i][MAX_ARG_LEN - 1] = '\0';
currentToken = strtok(NULL, " \t\n");
}
delete[] line_copy;
return cmd;
}
Support for built-in commands cd and exit is implemented in the do_builtin function:
int do_builtin(command* cmd) {
if (strcmp(cmd->argv[0], "exit") == 0) {
exit(SUCCESS);
}
if (cmd->argc == 1) {
return chdir(getenv("HOME")); // cd with no arguments
} else if (cmd->argc == 2) {
return chdir(cmd->argv[1]); // cd with one argument
} else {
fprintf(stderr, "cd: Too many arguments\n");
return ERROR;
}
}
The shell uses execv to execute external commands, following a search in the PATH environment variable:
bool find_full_path(command* cmd) {
char* path_env = getenv("PATH");
if (!path_env) return false;
char* path_copy = new char[strlen(path_env) + 1];
strcpy(path_copy, path_env);
char* currentToken = strtok(path_copy, ":");
while (currentToken != NULL) {
char full_path[MAX_LINE_SIZE];
snprintf(full_path, sizeof(full_path), "%s/%s", currentToken, cmd->argv[0]);
struct stat buffer;
if (stat(full_path, &buffer) == 0 && S_ISREG(buffer.st_mode)) {
delete[] cmd->argv[0];
cmd->argv[0] = new char[strlen(full_path) + 1];
strcpy(cmd->argv[0], full_path);
delete[] path_copy;
return true;
}
currentToken = strtok(NULL, ":");
}
delete[] path_copy;
return false;
}
Memory is managed using new and delete throughout the project, ensuring no leaks.
history or export.|), redirections (>, <), and quotes.Unit tests are provided in the tests.cpp file to verify the shell’s functionality. The tests cover parsing, built-in commands, external command execution, and memory management. To run the tests, compile the project with the tests target:
make tests
Tests are provided using the Google Test framework. You might need to install the Google Test library to run the tests.
This shell was built as part of a project to understand and implement fundamental system programming concepts in C. It incorporates concepts like dynamic memory allocation, process creation, and system calls, inspired by Unix-based shell functionality.
Son Nguyen
GitHub: hoangsonww
Created with ❤️ by Son Nguyen in 2024.