This project is about creating a simple shell, developed by me (adbouras) and (eismail).
In computer science, a shell is a user interface that allows users to interact with the operating system. It can be command-line based or graphical, but it’s most commonly associated with command-line interfaces (CLIs). Here are some key points about shells:
-
Types of Shells:
Command-Line Shells
: These include text-based interfaces where users type commands to perform tasks. Examples are: Bash (Bourne Again SHell): Commonly used on Linux and macOS.
Zsh (Z Shell): An extended version of Bash with more features.
PowerShell: A powerful shell for Windows with scripting capabilities.
Command Prompt: The traditional Windows shell for command-line operations.Graphical Shells
: These provide a graphical user interface (GUI) for users to interact with the system. Examples include desktop environments like GNOME or KDE.Usage
: Shells are used for a variety of tasks, including file manipulation, program execution, and system management. They are particularly useful for developers and system administrators who need to perform repetitive tasks or automate workflows.Customization
: Many shells support customization through configuration files (like .bashrc or .zshrc), allowing users to set aliases, functions, and appearance settings.
-
Our shell should:
- Display a prompt when waiting for a new command.
- Have a working history.
- Search and launch the right executable (based on the PATH variable or using a relative or an absolute path).
- Avoid using more than one global variable to indicate a received signal. Consider the implications: this approach ensures that your signal handler will not access your main data structures.
- Not interpret unclosed quotes or special characters which are not required by the subject such as (backslash) or ; (semicolon).
- Handle
’
(single quote) which should prevent the shell from interpreting the metacharacters in the quoted sequence. - Handle
"
(double quote) which should prevent the shell from interpreting the metacharacters in the quoted sequence except for $ (dollar sign). - Implement redirections:
<
should redirect input.>
should redirect output.<<
should be given a delimiter, then read the input until a line containing the delimiter is seen. However, it doesn’t have to update the history!>>
should redirect output in append mode.
- Implement pipes (
|
character). The output of each command in the pipeline is connected to the input of the next command via a pipe. - Handle environment variables ($ followed by a sequence of characters) which should expand to their values.
- Handle $? which should expand to the exit status of the most recently executed foreground pipeline.
- Handle
ctrl-C
,ctrl-D
andctrl-\
which should behave like in bash. - In interactive mode:
ctrl-C
displays a new prompt on a new line.ctrl-D
exits the shell.ctrl-\
does nothing.
- Your shell must implement the following builtins:
echo
with option-n
cd
with only a relative or absolute pathpwd
with no optionsexport
with no optionsunset
with no optionsenv
with no options or argumentsexit
with no options
For displaying the prompt and getting the command from the terminal, we have the right to work with the function read_line()
which does both actions at the same time.
#define PROMPT "minishell $ "
char *rl;
while (1)
{
rl = readline(PROMPT);
add_history(rl); // add rl to a working history.
printf("%s\n", rl);
}
clear_history(); // frees the working history.
NOTICE: You may encounter some issues with the readline library. Please ensure you follow these steps:
- Include the necessary tokensers in your code:
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
- Add the following linker command to your Makefile:
-lreadline
If you continue to experience problems with the readline library, you may need to install it manually: - Intall homebrew.
- Then, install the readline library using the following command
brew install readline
- Ensure you link it in your Makefile by adding:
LDFLAGS = -L/Users/<login>/.brew/opt/readline/lib
INCLUDES = -I/Users/<login>/.brew/opt/readline/include
Now that we've read the command, the next step is to parse it. I’ve decided to proceed with lexing.
A lexer, or lexical analyzer, is essential in converting a string or code into a sequence of tokens. Tokens are categorized strings or symbols that represent the fundamental components of a language, such as keywords, operators, and identifiers.
In essence, the lexer for this project is responsible for identifying and naming elements, such as recognizing "WORD" for a word, white space for a "W_SPACE" and "PIPE" for a pipe.
-
This is the structure that we went with:
typedef struct s_data { t_token *tokens; t_exec *exec; } t_data;
tokens
: pointer to a linked list of tokens.exec
: pointer to execution linked list.
-
This is the tokens structure:
typedef struct s_token { char *content; int len; t_type type; t_state state; struct s_token *next; struct s_token *prev; } t_token;
content
: pointer to a string holding an element.len
: content lenght.type
: structur identifing element type.state
: structur identifing element state.next
: pointer to the next element.prev
: pointer to the previous element.
-
Type structure:
typedef enum e_type { WORD = 0, W_SPACE = ' ', D_QUOTE = '\"', S_QUOTE = '\'', PIPE = '|', REDIR_IN = '<', REDIR_AND, REDIR_OUT = '>', REDIR_APP, ENV = '$', } t_type;
-
State structure:
e_state
is an identifier for a node satuse, if a node is inside a single or double quote,GENERAL
is niether.typedef enum e_state { IN_DQUOTE, IN_SQUOTE, GENERAL, } t_state;