This is my Minishell project: a small Bash-like shell written in C as part of the 42 common core. It parses and executes user commands, handles pipes and redirections, and manages signals and child processes with proper system calls.
We had to rebuild key parts of a shell from scratch: quoting, environment variables, file descriptors, fork/exec logic, and more.
It was a deep dive into Unix internals and low-level system programming. Everything from process creation to memory management was handled manually, using a range of standard and POSIX libraries (such as unistd.h, fcntl.h, signal.h, and readline.h) to interact with the system. Overall, this project gave me a much clearer understanding of what a shell actually does under the hood.
To compile the program, navigate to the project root and run:
make
# or
make bonusThen you can start the program like this:
./minishellIn the following examples I will try to present most of the features supported, but not everything is shown here.
The minishell project is covering a lot of ground, and the best way to check all of its features would be to use the program directly.
Basic
This example shows a basic echo command with a redirection to a file output.
Quoting forces a literal interpretation of special characters such as | or >.
Empty
This example is to test empty commands. A simple line return, nothing quoted, and space quoted.
Redirections
The next example is showing support of the multiple input redirections in random order, before and after the main command.
As you can see, only the last file is used for the redirection while the previous files are created if they don't exist.
Same for multiple output redirections. The redirections work without spaces or with quotes.
If no command is used, the file is emptied just like Bash would do.
Heredocs
This section demonstrates heredoc support. You can combine heredocs just as you can with simple redirections.
If a pipe ends the command line, heredocs are resolved first, and then the program displays the appropriate prompt for the remaining pipe.
Pipes
This example demonstrates a simple command using pipes.
This example shows heredoc behavior with pipes. As long as the last command ends with a pipe, a new prompt will open.
Once a command does not end with a pipe, the multiple commands are merged into a single one.
Echo
This first example will show the basic behavior of echo when used with multiple quotes mixed.
The echo builtin is also supporting the use of the -n flag.
We can go a little further by exporting a variable containing spaces in our environment.
Then depending on the quotes used, the expansion will vary, either splitting the variable or not.
Cd
The following examples will showcase the cd behavior.
If no argument is passed, cd will use HOME as long as it exists.
If HOME is not set, the program will return an error.
You can export a variable HOME in order to set it to the desired path.
Here we can see it fail on a locked directory, and a missing directory.
The following image is showing the cd command used with .. and . in accordance to the subject.
We use pwd to check that we are in the correct directory, even though the current working directory is updated directly in the prompt.
Exit
If a value contained in a long long is passed to the exit command, it will be used as the returned value.
That only works when using a numeric value.
And with a single argument.
If no value is passed to the exit command, then the last command status is returned.
Here we test this by first using ctrl + c to set the status to 130, then we exit. The returned value is indeed 130.
Errors
First is a simple example using an unknown command.
Here I used the ls command to show the possible file errors and the ways the program is dealing with them.
As you can see, the correct error messages and returned values are used, even when using a redirection or not.
Path
This example is used to show that removing the PATH will stop relative commands from executing, but absolute commands should still work.
Adding a PATH again should allow for the execution of the commands found in that PATH.
Wildcards
Finally we can see the wildcard support in the following examples. First we check that we can get all the files expanded.
Then only the folders, only the hidden files, only the hidden folders, only the hidden folders containing it at the end, and finally only the hidden folders containing i followed by a t at the end.
We also can check the wildcard behavior when used with redirections, or quotes.
All projects from my 42 cursus are preserved in their state immediately following their final evaluation. While they may contain mistakes or stylistic errors, I've chosen not to alter them. This approach provides a clear and authentic timeline of my progress and learning journey as a programmer.
Ctrl + C in a blocking command, like cat, doesn't return before a new prompt.

One easy way to fix this would be to add this function ft_handle_wait_signal():
void ft_handle_wait_sigint(int signal)
{
(void)signal;
write(1, "\n", 1);
rl_replace_line("", 0);
rl_on_new_line();
}Then modify ft_wait_signals() to use the new function we just made:
void ft_wait_signals(void)
{
struct sigaction sa;
struct sigaction sa_quit;
- sa.sa_handler = SIG_IGN;
+ sa.sa_handler = ft_handle_wait_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sa_quit.sa_handler = SIG_IGN;
sigemptyset(&sa_quit.sa_mask);
sa_quit.sa_flags = 0;
sigaction(SIGQUIT, &sa_quit, NULL);
}Killing a program like vim from inside minishell can leave the terminal in a messy or unusable state (broken prompt). This happens because such programs modify the terminal's settings (termios), and when they’re killed abruptly, those settings aren’t reset properly.

This can be fixed by saving the current terminal attributes using tcgetattr() before launching the command, and restoring them afterward using tcsetattr().
While this is a known edge case, it's quite niche for this project’s scope so I chose not to handle it further.
This project was developed in collaboration with another student, who contributed to the implementation of built-in commands, signal handling, and provided valuable debugging support.


