This project is about creating a simple shell. Yes, your own little bash. You will learn a lot about processes and file descriptors.
⚠️ Remember that even if there is a nice README with cool emojis, this project has been made by (very good though) students and is probably not perfect
- dev_utils.c: ❓
- execute_tree.c: ✅
- executer.c: ❓
- ft_builtins.c: ❓
- ft_freefuncs.c: ❓
- ft_getpathname.c: ❓
- ft_splitnotstr.c: ❓
- ft_strjoinfree.c: ❓
- minishell.c: ❓
- parse.c: ❓
- tree.c: ✅
git clone https://github.com/leogaudin/minishell.git
cd minishell
make && ./minishellIf you have an error when running make, it is probably because you do not have the readline library installed on your computer, or that it is not well linked.
There are a few ways to fix this, but ours was:
- Install vagrant with brew
brew install vagrant- Install the
readlinelibrary with brew
brew install readline- Create a symlink to the library
ln /opt/homebrew/Cellar/readline/8.2.1/lib/libreadline.8.dylib /usr/local/lib/libreadline.8.dylibint ft_cd(t_fullcmd fullcmd, char ***env)
{
char *path;
char *oldpath;
char *home;
path = NULL;
home = ft_getenv("HOME", *env);
oldpath = ft_strdup(getcwd(NULL, 0));
determine_path(&path, home, fullcmd, env);
if (path == NULL)
return (ft_printf("cd: HOME not set\n"), 1);
if (change_path(path, fullcmd))
return (1);
path = ft_strdup(getcwd(NULL, 0));
update_pwd(path, oldpath, env);
return (free(path), free(oldpath), free(home), 0);
}The cd command needs to take into account the current directory to be able to go back to it if the user wants to go back to the previous directory.
The determine_path function is used to handle the cases not handled by the chdir function, such as cd - or expanding the ~ token to /Users/user_directory.
The change_path function is used to change the current directory to the one specified by the user by calling the chdir function. It also handles the errors returned by chdir, for example if the directory does not exist.
It then updates the PWD and OLDPWD environment variables by calling the update_pwd function.
int ft_pwd(char **env)
{
char *wd;
wd = ft_getenv("PWD", env);
if (write(STDOUT_FILENO, wd, ft_strlen(wd)) < 0)
return (ft_putendl_fd(strerror(errno), STDERR_FILENO), -1);
if (write(STDOUT_FILENO, "\n", 1) < 0)
return (ft_putendl_fd(strerror(errno), STDERR_FILENO), -1);
return (0);
}pwd simply fetches the value of the PWD environment variable and prints it.
int ft_env(t_fullcmd fullcmd, char ***env)
{
int i;
i = 0;
if (fullcmd.argums[1] != NULL)
return (ft_putendl_fd("The builtin env does not take arguments or options.", STDERR_FILENO), -1);
while ((*env)[i])
{
printf("%s\n", (*env)[i]);
i++;
}
return (0);
}The env command simply iterates through the environment variables fetched at the entry of the program and prints them.
void ft_exit(t_fullcmd fullcmd, char ***env)
{
int i;
(void)env;
i = 0;
ft_putstr_fd("exit\n", STDOUT_FILENO);
if (fullcmd.argums[1] != NULL && fullcmd.argums[2] != NULL)
return (ft_putendl_fd("exit: too many arguments", STDERR_FILENO), (void)0);
if (fullcmd.argums[1] != NULL)
{
while (fullcmd.argums[1][i])
{
if (ft_isdigit(fullcmd.argums[1][i]) == 0)
{
ft_putendl_fd("exit: numeric argument required", STDERR_FILENO);
exit(255);
}
i++;
}
exit(ft_atoi(fullcmd.argums[1]));
}
exit(0);
}The exit command is used to exit the shell. It takes into account the exit status specified by the user, and if it is not specified, it exits with the 0 exit status.
void sigint_handler(int sig)
{
(void)sig;
ft_putchar_fd('\n', 1);
rl_replace_line("", 0);
rl_on_new_line();
rl_redisplay();
}Handling the Ctrl-C signal is made easier by the use of the readline library, allowing us to clear the current line and display a new prompt seamlessly.
Ctrl-D actually prints an EOF character, we therefore only need to check if the line read by readline is empty to exit the shell.
The && and || operators actually need to be handled carefully, as they can be chained together and that every command in the chain depends on the exit status of the previous one.
To achieve so, the most efficient way is to use an Abstract Syntax Tree (AST).
Abstract Syntax Trees are often used to represent mathematical operations with priorities. For example
In our case, we will use an AST to represent the commands and operators entered by the user. For example, the command ls && pwd || echo "Hello World" would be represented as:
- The
lscommand is executed. - The
pwdcommand is executed if thelscommand exited with a 0 exit status. - The
echo "Hello World"command is executed if one of thelsorpwdcommands exited with a non-zero exit status.
By traversing the AST, we can execute the commands in the right order, and we can also handle the && and || operators' particularities properly.
🙇🏻♂️ This project has been done in great collaboration with ysmeding.