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.
- Overview
- Features
- File Structure
- Compilation and Execution
- Usage
- Code Highlights
- Future Improvements
- Testing
- Acknowledgments
- Author
The Shell Implementation in C provides fundamental features for executing user commands. It includes:
- Parsing user input into arguments.
- Identifying commands (both built-in and external).
- Handling the
PATH
environment variable to locate executables. - Executing commands in a child process while the parent process waits.
- Memory management and error handling to avoid leaks and crashes.
- Support for built-in commands like
cd
andexit
. - A simple interactive shell interface for user input and output.
- A Makefile for easy compilation and testing.
- Unit tests to verify the shell's functionality.
- Compatibility with GNU/Linux systems.
- Command Parsing: The shell parses user input into tokens (arguments) and constructs a
command
structure. - Built-in Commands:
cd
: Changes the current working directory.exit
: Exits the shell.
- External Command Execution: Supports executing external programs by searching for their full paths using the
PATH
environment variable. - Memory Management: Implements dynamic memory allocation using
new
anddelete
, ensuring no memory leaks or dangling pointers. - Error Handling: Comprehensive error handling for memory allocation, invalid commands, and execution errors.
shell.h
: Header file containing definitions, constants, and function prototypes.shell.c
: Main implementation of the shell logic.Makefile
: File for building the project usingmake
.tests.cpp
: Unit tests to verify the shell's functionality (optional, if included).
- Operating System: Must be GNU/Linux system (might be incompatible with Windows or macOS).
- C Compiler: GCC or Clang (with C++ compatibility if using
new
/delete
). - Make: Build system utility.
- Valgrind (optional): For memory leak detection.
- Clone or download the project to your local machine.
- Navigate to the project directory.
- Compile the code:
make
- Run the shell:
./main
To ensure there are no memory leaks:
make valgrind
- Start the shell:
./main
- Enter a command:
- Example of a built-in command:
or
cd /home/user
pwd
- Example of an external command:
ls -l
- Example of a built-in command:
- Exit the shell:
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.
- Enhanced Built-in Commands: Add more built-ins like
history
orexport
. - Background Processing: Add support for background tasks.
- Advanced Parsing: Handle pipes (
|
), redirections (>
,<
), and quotes. - Interactive Features: Improve user experience with command auto-completion and history navigation.
- Error Reporting: Provide more descriptive error messages for better debugging.
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.