DEV Community

Ertugrul
Ertugrul

Posted on

How I Built a Simple Shell in C – A Beginner's Guide to System Programming (3/3)

Part 3: Executing Commands in a Custom Shell (C Programming)

In this final part of my custom shell project, we’ll explore how to execute user commands after they’ve been parsed. At this point, the user input has been successfully split into arguments — now it’s time to take action! 💥


Understanding the execute_command Function

The core function here is:

void execute_command(char **args, int argc, Command *command, int command_count) 
Enter fullscreen mode Exit fullscreen mode

✅ Matches user input with a predefined command
✅ Executes the corresponding function pointer
✅ Prints an error message if no command matches

Let’s break it down 👇

for (int i = 0; i < command_count; i++) { if (strcmp(args[0], command[i].name) == 0) { command[i].action(args, argc); return; } } printf("Unknown commnad: %s\n", args[0]); 
Enter fullscreen mode Exit fullscreen mode
  1. Command Matching: The shell loops through an array of Command structs and compares the input with registered command names.
  2. Function Pointers: If a match is found, the corresponding function is called via a pointer.
  3. Error Feedback: If no match is found, the shell notifies the user that the command is unknown (with a small typo on purpose? 😉).

The Command Structure

Each command is a struct that looks like this:

typedef struct Command { char *name; void (*action)(char **args, int argc); } Command; 
Enter fullscreen mode Exit fullscreen mode
  • name: the actual command (like "say" or "date")
  • action: a pointer to the function that gets executed

This structure is super handy for adding new commands without writing extra if-else statements.


A Look at Some Commands

Let’s check out a few implemented commands from the execute.c file:

🗨️ say_action

void say_action(char **args, int argc) { if (argc > 1) { for (int i = 1; i < argc; i++) { printf("%s ", args[i]); } } printf("\n"); } 
Enter fullscreen mode Exit fullscreen mode

Simply echoes back what the user types — a nice test command.

🗓️ date_command

Gives you the current date and time — optionally broken down into year, month, day, hour, minute, or second depending on flags like -dy, -dm, etc.

📂 clone_file_command

Copies a file from one location to another using binary read/write.

✂️ cut_file_command

Cuts a file (moves it) to a new location and removes the original — uses file streams and path manipulation.

📁 list_of_directory

Recursively lists directory contents in a tree-like format. Really cool!


Function Pointer Magic 🧙

Using function pointers instead of conditionals makes the design much more scalable. You can add a new command like this:

{"new_command", new_command_action} 
Enter fullscreen mode Exit fullscreen mode

…and implement it as a regular function. Done!


Memory Considerations

Each command function takes char **args and int argc — just like main(int argc, char **argv) — keeping things familiar and flexible.

Also, as shown in the main loop:

free_args(args, argc); free(command); 
Enter fullscreen mode Exit fullscreen mode

Memory is properly cleaned up after every command execution ✅


Why This Approach?

📌 Modular Design: Each command is self-contained and easy to test or extend.
📌 Dynamic Execution: Commands can be dispatched efficiently using function pointers.
📌 User-Friendly Feedback: Invalid inputs are handled gracefully.
📌 Scalable Structure: Adding a new command takes just 2 lines in the main file!


Wrap-Up: Building a Custom Shell 🧠

Over the past three parts, we built a functional, modular shell in C:

  • Part 1: Read user input dynamically
  • Part 2: Parse input into arguments
  • Part 3 (this one): Execute commands based on user input

Next step? Add more features, maybe some pipes, or even background execution!

📂 GitHub: https://github.com/Ertugrulmutlu/shell_of_mine

Did you find this series helpful? What feature should I add next? Let me know! 👇

Top comments (0)