Skip to content

Latest commit

 

History

History
2116 lines (1406 loc) · 86.4 KB

File metadata and controls

2116 lines (1406 loc) · 86.4 KB


Logo

Documentation of frankenshell

Version: 2.1
Date: 2024-01-10

Buy Me A Coffee
If this documentation is helpful for you, feel free to buy me a coffee ;)


Legend

🔝 Link to top of the page / table of content
🔗 Link to external website
◀️ Link to parent section
📖 Link to another section
💻 Link to related.c file
📄 Link to related.h file
💡 Hint
✏️ Example (foldable section)
⚠️ Warning / Important
✅ Yes / Success
❌ No / Fail



🔝 Table of Contents

  1. Introduction
  2. Installation
  3. Usage [Info Mode]
  4. Definitions
  5. Syntax [Quotes, Seperators]
  6. Structs [t_mbox]
  7. Environment Variables
  8. Builtins [42, cd, echo, env, exit, export, help, history, infomode, pwd, unset]
  9. Exit Status
  10. Signals
  11. How Frankenshell Operates
    1. Initialization
    2. Processing a cycle
      1. String Management
        1. Trim Input
        2. Mark Empty Quotes [Quote State]
        3. Shift Separators
        4. Variable Expansion
      2. Tokenizing
      3. Parsing
      4. Executing
        1. Setup Command
        2. Setup Redirections
        3. Setup Heredoc
        4. Run Command
      5. Cleanup Cycle
    3. Termination
  12. Known Bugs
  13. Acknowledgments


🔝 Introduction

As ChatGPT said in 2023:

Minishell is a 🔗42 school project that aims to create a simplified Unix shell using 🔗bash as a reference. The idea behind Minishell is to develop a basic command-line interface (CLI) that can execute simple commands and handle input/output redirection. It's meant to serve as a learning exercise for students to gain a fundamental understanding of how shells work by implementing core features like parsing user input, managing processes, handling signals, creating builtins and executing system commands.


🔝 Installation

$ git clone https://github.com/ahokcool/frankenshell.git  # Clone
$ cd frankenshell                                         # Change directory
$ make                                                    # Compile
$ ./frankenshell [OPTIONS]                                # Run (Option: --info)
$ echo "Let's goooooo!"                                   # Use
	Let's goooooo!
$ exit                                                    # Exit

💡 make stats provides you with some information about the files and the number of functions.


🔝 Usage

frankenshell is designed to mimic the behavior of the traditional 🔗bash shell. So feel free to use it like bash.

Example


To activate the info mode you can:

If the info mode is activated, frankenshell will print the following information during runtime:

* Also as 📖readable strings

✏️ Example echo "Hello" $USER "" '!' | wc -l     
$ ./frankenshell -i
 -------------------------------------------------------------------------------- 
|                              INFO MODE ACTIVATED!                              |
 -------------------------------------------------------------------------------- 
frankenshell--> echo "Hello" $USER "" '!' | wc    -l     
 -------------------------------------------------------------------------------- 
|                                  INPUT STATES                                  |
 -------------------------------------------------------------------------------- 
original:			(echo "Hello" $USER "" '!' | wc    -l     )

trimmed:			(echo "Hello" $USER "" '!' | wc    -l)

empty quotes:	shifted:	(echo "Hello" $USER �� '!' | wc    -l)
		readable:	(echo "Hello" $USER E_ '!' | wc    -l)

shifted:	shifted:	(echo��Hello��$USER�����!����wc����-l)
		readable:	(echo_DHelloD_$USER_E__S!S_P_wc____-l)

expanded:	shifted:	(echo��Hello��astein�����!����wc����-l)
		readable:	(echo_DHelloD_astein_E__S!S_P_wc____-l)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                   TOKENIZER                                    |
 -------------------------------------------------------------------------------- 
type:(0) 	 token:(echo)
type:(0) 	 token:(Hello)
type:(0) 	 token:(astein)
type:(0) 	 token:()
type:(0) 	 token:(!)
type:(1) 	 token:(|)
type:(0) 	 token:(wc)
type:(0) 	 token:(-l)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                     PARSER                                     |
 -------------------------------------------------------------------------------- 

              [arg] (-l)

       [cmd] (wc)

[|] (|)

                                   [arg] (!)

                            [arg] ()

                     [arg] (astein)

              [arg] (Hello)

       [cmd] (echo)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                                 (cmd count: 2)                                 |
 -------------------------------------------------------------------------------- 
1
 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                              LAST EXIT STATUS: 0                               |
 -------------------------------------------------------------------------------- 
==================================================================================

frankenshell--> 

✏️ Example ls -l -a
$ ./frankenshell -i
 -------------------------------------------------------------------------------- 
|                              INFO MODE ACTIVATED!                              |
 -------------------------------------------------------------------------------- 
frankenshell--> ls -l -a
 -------------------------------------------------------------------------------- 
|                                  INPUT STATES                                  |
 -------------------------------------------------------------------------------- 
original:			(ls -l -a)

trimmed:			(ls -l -a)

empty quotes:	shifted:	(ls -l -a)
		readable:	(ls -l -a)

shifted:	shifted:	(ls�-l�-a)
		readable:	(ls_-l_-a)

expanded:	shifted:	(ls�-l�-a)
		readable:	(ls_-l_-a)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                   TOKENIZER                                    |
 -------------------------------------------------------------------------------- 
type:(0) 	 token:(ls)
type:(0) 	 token:(-l)
type:(0) 	 token:(-a)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                     PARSER                                     |
 -------------------------------------------------------------------------------- 

              [arg] (-a)

       [arg] (-l)

[cmd] (ls)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                                 (cmd count: 1)                                 |
 -------------------------------------------------------------------------------- 
total 276
drwxrwxr-x 10 astein astein   4096 Jan 10 11:10 .
drwxrwxr-x  3 astein astein   4096 Dez 15 14:03 ..
-rwxrwxr-x  1 astein astein   6323 Dez 15 20:15 art.sh
-rwxrwxr-x  1 astein astein   1498 Dez 15 20:03 count_stats.sh
drwxrwxr-x  2 astein astein   4096 Jan  9 14:42 docs
-rw-rw-r--  1 astein astein      1 Jan 10 11:35 file
-rwxrwxr-x  1 astein astein 208296 Jan 10 09:01 frankenshell
drwxrwxr-x  8 astein astein   4096 Jan  9 20:50 .git
-rw-rw-r--  1 astein astein    215 Dez 15 20:03 .gitignore
drwxrwxr-x  3 astein astein   4096 Jan 10 11:55 images
drwxrwxr-x  2 astein astein   4096 Jan  7 11:45 includes
drwxrwxr-x  4 astein astein   4096 Jan 10 09:01 libft
-rw-rw-r--  1 astein astein   3429 Jan  7 14:20 Makefile
drwxrwxr-x  6 astein astein   4096 Jan 10 09:01 obj
-rw-rw-r--  1 astein astein   1638 Jan  6 17:33 README1.md
-rw-rw-r--  1 astein astein   2587 Jan  9 16:30 README.md
drwxrwxr-x  6 astein astein   4096 Jan  7 14:17 src
drwxrwxr-x  2 astein astein   4096 Dez 15 20:22 .vscode
 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                              LAST EXIT STATUS: 0                               |
 -------------------------------------------------------------------------------- 
==================================================================================

frankenshell--> 
✏️ Example echo the home dir of $USER is storred in '$HOME'
$ ./frankenshell -i
 -------------------------------------------------------------------------------- 
|                              INFO MODE ACTIVATED!                              |
 -------------------------------------------------------------------------------- 
frankenshell--> echo the home dir of $USER is storred in '$HOME'
 -------------------------------------------------------------------------------- 
|                                  INPUT STATES                                  |
 -------------------------------------------------------------------------------- 
original:			(echo the home dir of $USER is storred in '$HOME')

trimmed:			(echo the home dir of $USER is storred in '$HOME')

empty quotes:	shifted:	(echo the home dir of $USER is storred in '$HOME')
		readable:	(echo the home dir of $USER is storred in '$HOME')

shifted:	shifted:	(echo�the�home�dir�of�$USER�is�storred�in��$HOME�)
		readable:	(echo_the_home_dir_of_$USER_is_storred_in_S$HOMES)

expanded:	shifted:	(echo�the�home�dir�of�astein�is�storred�in��$HOME�)
		readable:	(echo_the_home_dir_of_astein_is_storred_in_S$HOMES)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                   TOKENIZER                                    |
 -------------------------------------------------------------------------------- 
type:(0) 	 token:(echo)
type:(0) 	 token:(the)
type:(0) 	 token:(home)
type:(0) 	 token:(dir)
type:(0) 	 token:(of)
type:(0) 	 token:(astein)
type:(0) 	 token:(is)
type:(0) 	 token:(storred)
type:(0) 	 token:(in)
type:(0) 	 token:($HOME)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                     PARSER                                     |
 -------------------------------------------------------------------------------- 

                                                               [arg] ($HOME)

                                                        [arg] (in)

                                                 [arg] (storred)

                                          [arg] (is)

                                   [arg] (astein)

                            [arg] (of)

                     [arg] (dir)

              [arg] (home)

       [arg] (the)

[cmd] (echo)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                                 (cmd count: 1)                                 |
 -------------------------------------------------------------------------------- 
the home dir of astein is storred in $HOME
 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                              LAST EXIT STATUS: 0                               |
 -------------------------------------------------------------------------------- 
==================================================================================

frankenshell--> 
✏️ Example << $DONT_EXPAND cat | wc -l
$ ./frankenshell -i
 -------------------------------------------------------------------------------- 
|                              INFO MODE ACTIVATED!                              |
 -------------------------------------------------------------------------------- 
frankenshell--> << $DONT_EXPAND cat | wc -l
 -------------------------------------------------------------------------------- 
|                                  INPUT STATES                                  |
 -------------------------------------------------------------------------------- 
original:			(<< $DONT_EXPAND cat | wc -l)

trimmed:			(<< $DONT_EXPAND cat | wc -l)

empty quotes:	shifted:	(<< $DONT_EXPAND cat | wc -l)
		readable:	(<< $DONT_EXPAND cat | wc -l)

shifted:	shifted:	(���$DONT_EXPAND�cat���wc�-l)
		readable:	(II_$DONT_EXPAND_cat_P_wc_-l)

expanded:	shifted:	(��$DONT_EXPAND�cat���wc�-l)
		readable:	(II$DONT_EXPAND_cat_P_wc_-l)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                   TOKENIZER                                    |
 -------------------------------------------------------------------------------- 
type:(2) 	 token:(<)
type:(2) 	 token:(<)
type:(0) 	 token:($DONT_EXPAND)
type:(0) 	 token:(cat)
type:(1) 	 token:(|)
type:(0) 	 token:(wc)
type:(0) 	 token:(-l)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                     PARSER                                     |
 -------------------------------------------------------------------------------- 

              [arg] (-l)

       [cmd] (wc)

[|] (|)

       [cmd] (cat)

              [<<] ($DONT_EXPAND)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                                 (cmd count: 2)                                 |
 -------------------------------------------------------------------------------- 
frankendoc> Hello
frankendoc> World
frankendoc> $DONT_EXPAND
2
 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                              LAST EXIT STATUS: 0                               |
 -------------------------------------------------------------------------------- 
==================================================================================

frankenshell--> 
✏️ Example echo hi"" "" "" ""there
$ ./frankenshell -i
 -------------------------------------------------------------------------------- 
|                              INFO MODE ACTIVATED!                              |
 -------------------------------------------------------------------------------- 
frankenshell--> echo hi"" "" "" ""there
 -------------------------------------------------------------------------------- 
|                                  INPUT STATES                                  |
 -------------------------------------------------------------------------------- 
original:			(echo hi"" "" "" ""there
)

trimmed:			(echo hi"" "" "" ""there)

empty quotes:	shifted:	(echo hi"" �� �� ""there)
		readable:	(echo hi"" E_ E_ ""there)

shifted:	shifted:	(echo�hi�����������there)
		readable:	(echo_hiDD_E__E__DDthere)

expanded:	shifted:	(echo�hi�����������there)
		readable:	(echo_hiDD_E__E__DDthere)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                   TOKENIZER                                    |
 -------------------------------------------------------------------------------- 
type:(0) 	 token:(echo)
type:(0) 	 token:(hi)
type:(0) 	 token:()
type:(0) 	 token:()
type:(0) 	 token:(there)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                     PARSER                                     |
 -------------------------------------------------------------------------------- 

                            [arg] (there)

                     [arg] ()

              [arg] ()

       [arg] (hi)

[cmd] (echo)
==================================================================================

 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                                 (cmd count: 1)                                 |
 -------------------------------------------------------------------------------- 
hi   there
 -------------------------------------------------------------------------------- 
|                                    EXECUTOR                                    |
|                              LAST EXIT STATUS: 0                               |
 -------------------------------------------------------------------------------- 
==================================================================================

frankenshell--> 


🔝 Definitions

These definitions are used throughout this manual as follows.

builtin (📖builtins)
    A command that is implemented internally by the shell itself, rather than by an executable program somewhere in the file system.

exit status (📖exit status)
    The value returned by a command to its caller. The value is restricted to eight bits, so the maximum value is 255.

signal (📖signals)
    A mechanism by which a process may be notified by the kernel of an event occurring in the system.

token (📖tokenizing)
    A sequence of characters considered a single unit by frankenshell.

cycle (📖processing a cycle)
    The input is processed after the user hits the return key, which involves a series of steps: cleaning the input string, expanding variables, tokenizing, constructing an ast, setting up pipes, forking processes, and updating the exit status.

ast / tree (📖parsing)
    The parser in Frankenshell constructs an abstract syntax tree (AST).


🔝 Syntax

The syntax of frankenshell is designed to mimic the syntax of the traditional 🔗bash shell.

Note that redirections and arguments can be in any order. The only rule is that the filename (or limiter for heredoc) must appear immediately after the redirection.

✏️ Examples
# The following examples are all behaving the same way,
# which is to write "Hello World" into the file "file".

$ echo Hello World > file
$ echo Hello > file World
$ echo > file Hello World

🔝 ◀️ Quotes

  • Single Quotes:
    Enclosing text in single quotes ' prevents the shell from interpreting any metacharacters within the quoted sequence.
  • Double Quotes:
    Using double quotes " also prevents interpretation of metacharacters, except for the dollar sign $, which is used for 📖variable expansion.

If you use single quotes inside double quotes, the single quotes will be interpreted as normal characters and vice versa.

💡 The outer quotes are always the contextual quotes.
💡 A contextual quote (single or double) must always be closed with the matching quote.
⚠️ If contextual quotes are not closed, frankenshell prints an error and updates the 📖exit status to 2.

✏️ Examples
$ echo "this single quote: ' is inside contextual quotes and therefore not contextual!"
this single quote: ' is inside contextual quotes and therefore not contextual!

$ echo 'this double quote: " is inside contextual quotes and therefore not contextual!'
this double quote: " is inside contextual quotes and therefore not contextual!

$ echo "this is a pipe symbol | inside contextual quotes"
this is a pipe symbol | inside contextual quotes

$ echo "this single quote ' doesn't close the double quote
frankenshell: syntax error: unclosed quotes

🔝 ◀️ Separators

The following characters are used as seperators for the input string:

Symbol Description
' " contextual quotes
| pipe
< redirection in
<< frankendoc aka heredoc
> redirection out
>> redirection out append

⚠️ frankenshell doesn't support those seperators:
;, &, &&,||, (, ), {, }, *, [, ], ~, !, =, +, -, /, %, ^, @, #, :, ,, .


🔝 Structs 📄

All structs are defined int the header file 📄structs.h.

The struct mbox is the main structure of the program: it is being passed as an argument to most of the functions and contains all the info needed for the program to work.
The file 💻manage_mbox.c contains the functions for initializing and destroying the mbox instance.

typedef struct s_mbox
{
    char    *inp_orig;              // (  echo $USER "" > file)
    char    *inp_trim;              // (echo $USER "" > file)
    char    *inp_eq;                // (echo $USER E_ > file)
    char    *inp_shift;             // (echo $USER E_ O file)
    char    *inp_expand;            // (echo astein E_ O file)
    int	    consecutive_lt;         // << lol << lol
    bool    syntax_err_encountered; // track the first error
    t_env   *env;                   // env vars as linked list
    t_list  *history_lst;           // history as linked list
    t_token *tokens;                // tokens as linked list
    t_token *tmp_token;             // temp token for building ast
    t_ast   *ast;                   // root of the ast
    t_ast   *tmp_node;              // temp node for building ast
    t_exec  exec;                   // execution data
    int	    count_cycles;           // cycles for heredoc error msg
    t_bool  info_mode;              // info mode on/off
} t_mbox;


🔝 Environment Variables 💻

This is general information about environment variables in frankenshell.

Related Sections:
📖variable expansion 📖heredoc 📖env 📖export 📖unset 📖exit status

On 📖programm start a linked list (t_env) will be created from the enviromental variables (char **env) and stored in the 📖t_mbox struct. Variables represent a simple key-value pair. Key and value are strings. The key is always unique and the value can be empty. On 📖termination, the linked list is freed.

With the following 📖builtin commands variables can be...

A key has to match the following 🔗regex:

^[a-zA-Z_]+[a-zA-Z0-9_]*$

Explanation:
^               # start of string
[a-zA-Z_]       # first char must be a letter or '_'
+               # one or more chars
[a-zA-Z0-9_]*   # zero or more letters, numbers or '_'
$               # end of string

The following functions are implemented in the 💻env_vars file:

MANAGEMENT
 	'initialize_vars'   # creates the ll on startup
	'free_vars'         # frees the ll
	'free_var'          # free the given node
	'var_add_back'      # adds the node to the end of the ll

READ FUNCTIONS
 	'is_var'            # checks if the argument 'key' is present in list
	'get_var_node'      # returns a pointer to the node with the given key
	'get_var_value'     # returns a pointer to the value of the given key
	'get_vars_count'    # returns the amount of nodes in the ll
	'get_env_as_matrix' # creates a matrix of strings from the ll

WRITE FUNCTIONS
	'set_var_value'     # updates/creates node with the given key and value
	'set_var_value_int' # updates/creates node with the given key and int value
	'increment_shlvl'   # increases the value of the 'SHLVL' variable
	'unset_var'         # removes the node with the given key from the ll

💡 The linked list will be used for the execve function call. Refer to section (📖run command).
💡 The linked list will be used for storing the 📖exit status.


🔝 Builtins

Each builtin command in frankenshell is detailed below with specific information and examples:

Command File(s) Description
📖42 💻42.c 42 it is ;)
📖cd 💻cd.c Changes the current directory.
📖echo 💻echo.c Displays a line of text.
📖env 💻env.c Displays the environment variables.
📖exit 💻exit.c Exits frankenshell.
📖export 💻export.c
💻export_utils.c
Sets or exports environment variables.
📖help 💻help.c Displays this documentation page on github
📖history 💻history.c Displays the command history.
📖infomode 💻infomode.c Toggles the info mode.
📖pwd 💻pwd.c Prints the working directory.
📖unset 💻unset.c Unsets environment variables.

🔝 ◀️ 42 (builtin)

The builtin 42 displays a 42 logo to STDOUT (or its redirection).

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n (all args will be ignored)
Exit Status 0
Affected Variables [NONE]
File 💻42.c
Examples
CMD STDOUT Explanation
42 [the 42 logo]
42 foo bar [the 42 logo] all args will be ignored
Screenshot

42


🔝 ◀️ cd (builtin)

The builtin cd runs a few checks to ensure the provided path is valid. Once it's all good, it uses the external function chdir to change the current working directory (wd) to this new path. At the same time, it updates the PWD variable to the new directory and OLDPWD to the previous one (if they exist).

⚠️ If the external function chdir fails, an error message is printed and the exit status is set to 1.
💡 If PWD and/or OLDPWD are absent, the function operates normally and skips setting these variables.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 - 1
Argument Format absolute or relative path
Exit Status 0 1
Affected Variables HOME OLDPWD PWD
File 💻cd.c
Examples
CMD STDERR Exit Status Explanation Affected Variables1
cd 0 if HOME is set HOME OLDPWD PWD
cd cd: HOME not set 1 if HOME is not set
cd "" 0 empty argument;
wd doesn't update
cd valid_path 0 wd updates to
./valid_path
OLDPWD PWD
cd . 0 wd doesn't update;
OLDPWD updated!
OLDPWD
cd .. 0 wd updates to
parent folder
OLDPWD PWD
cd foo bar cd: too many arguments 1
cd noExist frankenshell: cd: noExist: No such file or directory 1
cd noPermDir frankenshell: cd: noPermDir: Permission denied 1
cd file frankenshell: cd: file: Not a directory 1

1 Bold variables will be updated.


🔝 ◀️ echo (builtin)

The builtin echo outputs the strings it is given as arguments, seperated by one space each, to STDOUT (or its redirection). The flag -n can be used to prevent the trailing newline that is normally printed.

Attributes
Attribute Details
Flags -n
Number of Arguments 0 - n
Argument Format all ASCII chars allowed
Exit Status 0
Affected Variables [NONE]
File 💻echo.c
Examples
CMD STDOUT LINEBREAK
echo foo foo
echo "" '' foo foo
echo --n foo --n foo
echo -n foo foo
echo -n -nn -nnn foo foo
echo -n -nbar foo -nbar foo
echo -n foo -n foo -n

🔝 ◀️ env (builtin)

The builtin env outputs all variable key-value pairs of the linked list like key=value\n

💡 Refer to the section 📖environment variables for more information about environment variables.
⚠️ A 📖known bug exists with env and export.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0
Exit Status 0 127
Affected Variables [all]
File 💻env.c
Examples
CMD STDOUT STDERR Exit Status
env ...
USER=astein
HOME=/home/astein
LANGUAGE=en
...
0
env foo env: ‘foo’: No such file or directory 127
env foo bar env: ‘foo’: No such file or directory 127

🔝 ◀️ exit (builtin)

The builtin exit terminates the calling process, outputs exit to STDERR and if provided with a numeric argument, it sets the exit status to that argument's value. If the argument exceeds 255, it will be subjected to modulo 256.

💡 Refer to the section 📖exit status for more information about exit status.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 - 1
Argument Format numeric [max long long]
Exit Status 0 - 255
Affected Variables [NONE]
File 💻exit.c
Examples
CMD STDERR Exit Status Terminates
exit exit [doesn't update]
exit 42 exit 42
exit -42 exit 214
exit 424242 exit 50
exit foo exit
frankenshell: exit: foo: numeric argument required
2
exit foo 42 exit
frankenshell: exit: foo: numeric argument required
2
exit foo bar exit
frankenshell: exit: foo: numeric argument required
2
exit 42 42 exit
frankenshell: exit: too many arguments
11
exit 42 foo exit
frankenshell: exit: too many arguments
11

1 The exit status will only be updated to 1 if the last exit status was 0.


🔝 ◀️ export (builtin)

The builtin export updates (or creates) the enviromental variables inputed as key value pairs like key1=value1 key2=value2. If no argument is given, it will instead output all variables in alphabetical order.

💡 Refer to the section 📖environment variables for more information about environment variables.
⚠️ A 📖known bug exists with env and export.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 - n
Argument Format key=value
Exit Status 0 1
Affected Variables [key]
Files 💻export.c
💻export_utils.c
Examples
CMD Equal Sign Valid Key1 STDOUT STDERR Exit Status Affected Variables2
export ...
declare -x LANG="en_US.UTF-8"
declare -x LANGUAGE="en"
... 3
0
export @ frankenshell: export: `@': not a valid identifier 1
export @=foo frankenshell: export: `@=foo': not a valid identifier 1
export foo 0
export foo=bar 0 foo
export foo=bar school=42 0 foo school

1 Refer to the section 📖Environment Variables for more details about the key syntax.
2 Bold variables will be updated.
3 In alphabetical order with prefix declare -x .


🔝 ◀️ help (builtin)

The builtin help displays the documentation page on github.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n (all args will be ignored)
Exit Status N/A
File 💻help.c
Examples
CMD Explanation
help opens the documentation
help 42 opens the documentation
all args will be ignored

🔝 ◀️ history (builtin)

The builtin history outputs all previous user input in a numbered list to STDOUT (or its redirection). To do so, it uses a linked list stored in the 📖t_mbox struct. 📖Each cycle, the input string is added to the list. On 📖termination, the linked list is freed.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n (all args will be ignored)
Exit Status 0
File 💻history.c
Examples
CMD STDOUT
history 1 echo hi
2 ls
...
n history
history foo bar 1 echo hi
2 ls
...
n history

🔝 ◀️ infomode (builtin)

The builtin infomode toggles the info mode feature.

💡 Refer to the section 📖Info mode for more details about the info mode.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n (all args will be ignored)
Exit Status 0
File 💻infomode.c
Examples
CMD STDERR
infomode INFO MODE ACTIVATED!
infomode INFO MODE DEACTIVATED!

🔝 ◀️ pwd (builtin)

The builtin pwd outputs the current wd using the external function getcwd. Like in bash all arguments will be ignored.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n (all args will be ignored)
Exit Status 0
Affected Variables [NONE]
File 💻pwd.c
Examples
CMD STDOUT Explanation
pwd /mnt/daten/GIT/frankenshell
pwd foo bar /mnt/daten/GIT/frankenshell all args will be ignored

🔝 ◀️ unset (builtin)

The builtin unset deletes the corresponding variables.

💡 Refer to the section 📖environment variables for more information about environment variables.

Attributes
Attribute Details
Flags N/A
Number of Arguments 0 to n
Argument Format key
Exit Status 0
Affected Variables [key]
File 💻unset.c
Examples
CMD Explanation Exit Status Affected Variables
unset nothing happens 0
unset foo bar the variables foo and bar will be deleted 0 foo bar


🔝 Exit Status

The current exit status of minishell is stored as an node with the key ? in the linked list of 📖environment variables.

The exit status will be updated in the following cases:

  • on 📖startup to 0
  • unclosed 📖quotes to 2
  • syntax errors
  • a command is not found
  • a file is not found
  • a permission is denied
  • a child process terminates
  • a sigle 📖builtin finished execution
  • a 📖signal is received

Storing the exit status in the enviroment variables linked list simplifies the variable expansion of $?. $? always expands to the current exit status.

💡 Anyhow, if the linked list is used for execve, env or export the ? node will be ignored.


🔝 Signals 📄 💻

Each time a fork is happening all existing processes will be set to a specific signal status via the function conf_sig_handler in 💻signals.c. This signal status will be used to determine which signal handling should be used in the regarding process. On 📖programm start the signal status will be set to SIG_STATE_MAIN.

The following Signals are treated in frankenshell:

Shortcut Signal Name Description
CTRL + C SIGINT Interrupt signal
CTRL + \ SIGQUIT Quit signal
CTRL + D EOF An EOF is send to the input fd

The following chart shows how the signal status is set for each process (explained on an example): signal logic


The following table shows all possible signal states defined in 📄config.h

Signal State CTRL + C CTRL + \ CTRL + D
SIG_STATE_MAIN new promt
exit status 130
ignored exit frankenshell
SIG_STATE_PARENT \n Quit (core dumped)\n default
SIG_STATE_IGNORE ignored ignored default
SIG_STATE_CHILD default default default
SIG_STATE_CHILD_BUILTIN default defatult default
SIG_STATE_HD_CHILD exit heredoc ignored exit heredoc with warning



🔝 How Frankenshell Operates

The main tasks of frankenshell can be grouped into those steps:

💡 Below you can find a detailed description of each step.


🔝 ◀️ Initialization

On startup the program will initialize the 📖t_mbox struct. This struct contains all the information needed for the program to work. It is passed as an argument to most of the functions.

The following steps are executed during initialization:

  • creating a linked list for the 📖environment variables
  • creating an array for the 📖builtin commands to connect the command name with the function pointer
  • seting the 📖signal status to SIG_STATE_MAIN
  • setting the 📖exit status to 0
  • setting the 📖info mode to false (or true if the argument -i, --info is provided)
  • initializing all other filedescriptors and variables to -1, 0 or NULL
  • displaying the promt frankenshell-->
  • starting to read the input

🔝 ◀️ Processing a Cycle 💻

All the following steps are executed for each cycle:

💡 Below you can find a detailed description of each step.


🔝 ◀️ String Management

Step Example*
Finished Reading echo "Hello" $USER "" '!' | wc -l
📖Trim Input echo "Hello" $USER "" '!' | wc -l
📖Mark Empty Quotes echo "Hello" $USER E_ '!' | wc -l
📖Shift Separators echo_DHelloD_$USER_E__S!S_P_wc____-l
📖Variable Expansion echo_DHelloD_astein_E__S!S_P_wc____-l

*The 📖readable input string characters are displayed!

Readable Input Strings

Special Characers will be 📖shifted to negative ASCII values. Therefore they are nor longer printable. To still be able to display the input string, the following characters will be used to represent the special characters:

Character Description
_ No Space
E Empty Token
S Contextual Single Quotes
D Contextual Double Quotes
P Pipe
I Input Redirection
O Output Redirection

💡 Activate the 📖info mode to see all input string states during runtime


🔝 ◀️ Trim Input

First step is to trim the input. This means that all leading and trailing whitespaces are removed.

💡 Activate the 📖info mode to see all input string states during runtime


🔝 ◀️ Mark Empty Quotes

Quote State

If somwehre in frankenshell we loop through an input string, sometimes we need to know if we are inside or outside of contextual quotes. The quote state function updates the quote_state variable to the current state of the quotes. While traversing the input string, the quote state will be updated for each character. If the current character is inside a quote, the quote state will be set to the matching quote. If the current character is outside of a quote, the quote state will be set to OUT_Q.

Logic:

if quote_state is OUT_Q (the cur_char is outside of contextual quotes)
	if cur_char is a quote
		update quote_state to the cur_char
	else
		quote_state stays OUT_Q
else (the cur_char is inside of contextual quotes)
	if matching quote is found
		update quote_state to OUT_Q
	else
		quote_state stays the same          
✏️ Example echo "Hello" $USER "" '!' | wc -l     
Trimmed String:	echo "Hello 'astein", 'how are you?'
Quote State: 	OOOOODDDDDDDDDDDDDDOOOSSSSSSSSSSSSSO		//O = OUT_Q, D = IN_DOUBLE_QUOTES, S = IN_SINGLE_QUOTES
Marked String:	echo DHello 'asteinD, Show are you?S

Bash generates an empty token for a pair of empty contextual quotes if the two conditions are met:

  • the empty quotes are not inside another pair of quotes
    (refer to OUT_Q as the 📖quote state)
  • the empty quotes are surrounded by at least one whitespace character (on the beginning AND on the end of the string)

Empty quotes will be marked as EMPTY_TOKEN and NO_SPACE characters.

# define NO_SPACE       -125
# define EMPTY_TOKEN    -126
✏️ Examples
inp_trim inp_eq* empty quotes
echo "" echo E_
echo '' echo E_
echo """" echo E___
echo "" "" "'hi" echo E_ E_ "'hi"
echo hi"" echo hi""
echo hi'' echo hi''
echo ""hi echo ""hi
echo ''hi echo ''hi
echo '""' echo '""'
echo "''" echo "''"

* 📖Readable Input String

💡 Refer to the section 📖Shift Separators for a better understanding of the marked input string.
💡 Refer to the section 📖Readble Input String for a better understanding of the marked input string.
💡 Activate the 📖info mode to see all input string states during runtime


🔝 ◀️ Shift Separators

To prepare the input string for the tokenizer all seperating characters need to be found. To mark them frankenshell shifts their ASCII value by -126. This makes an easy check for all of them possible (ASCII < 0) without loosing their original value. A seperating char needs to be outside of any quotes to be shifted. Those are the characters which we consider as seperating characters:

  • whitespace ( , \n, \t, \v, \a, \b, \f, \r)
  • pipe (|)
  • redirections (<, >)
✏️ Examples
inp_trim inp_eq* inp_shift*
echo Hi > file ** echo_Hi_O_file
echo Hi >> file ** echo_Hi_OO_file
< file cat ** I_file_cat
<< file cat ** II_file_cat
echo Hi | wc ** echo_Hi_P_wc
echo """" > file echo E___ > file echo_E____O_file
echo "" "" "'hi" | wc echo E_ E_ "'hi" | wc echo_E__E__D'hiD_P_wc

* 📖Readable Input String
** No empty quotes, so the same then inp_trim.


🔝 ◀️ Variable Expansion

The 📖environment variable expansion works similar like in 🔗bash.

  • Variable Expansion happens if the variable is not inside single 📖quotes.
  • Whitespaces inside the variable value will be marked as NO_SPACE characters (📖). Therefore the 📖tokenizer can make multiple tokens out of it.
  • If the variable is part of the 📖heredoc limiter it won't be expanded! (e.g. << $USER cat)

💡 Activate the 📖info mode to see the expanded input string states during runtime.

✏️ Examples
Command Valid Key Expand Output
echo $USER astein
echo "$USER" astein
echo '$USER' $USER
<< $USER cat N/A Won't expand, so the EOF of the 📖heredoc will be $USER
✏️ Examples (Special Cases)
Special Case Valid Key Expand Explanation Output (example)
echo $@ hi first char of false key gets swallowed hi
echo $@@ hi first char of false key gets swallowed @ hi
echo $1HOME first char of false key gets swallowed HOME
$ no key $: command not found
echo $ hi no key $ hi
$? 📖exit status of the last command 42: command not found
echo $? 📖exit status of the last command 42
echo $?? 📖exit status of the last command 42?
echo $"USER" the " block the key;
contextual quotes get removed
USER
echo "foo $'BAR'" the ' block the key;
contextual quotes get removed
foo $'BAR'
echo 'foo $"BAR"' N/A inside contextual quotes ' -> no expansion foo $"BAR"
echo 'foo $BAR' N/A inside contextual quotes ' -> no expansion foo $BAR
echo foo$USER$HOME ✅ ✅ ✅ ✅ the second $ is not an allowed char of a key
therfore it terminates the first key.
fooastein/home/astein
echo foo $NOTEXIST bar the key doesn't exist;
expands to NULL
a b

Removal Of Whitespaces Of Expanded Variable Values

If there are whitespaces in the expanded Variable they will be marked as NO_SPACE characters. Therefore the tokenizer will make multiple tokens out of it. Quote Protection of whitespaces is not supported inside a variable. (like in 🔗bash).

✏️ Examples
frankenshell--> export foo="Hello           World"
frankenshell--> echo $foo
Hello World
frankenshell--> export foo="Hello'        'World" //tries a quote protect of whitespaces
Hello' 'World                                     //doesn't work 

Extract Limiter

The variable expansion for a heredoc limiter is a special case. Variable Expension is not allowed inside a heredoc limiter! Anyhow there some strange rules for the determine the limiter:

✏️ Examples
Case Limiter
(send to tokenizer)
Explanation Limiter 1
(to exit hd)
Var. expansion 2
(inside heredoc)
<< $USER cat $USER $USER
<< "FOO BAR" cat "FOO BAR" FOO BAR
<< "$FOO $BAR" cat "$FOO $BAR" $FOO $BAR
<< $'FOO' cat 'FOO' $ is followed by contextual ';
the $ will be removed
FOO
<< $"FOO" cat "FOO" $ is followed by contextual ";
the $ will be removed
FOO
<< $"FOO"$"BAR" cat "FOO""BAR" twice:
$ is followed by contextual quotes;
the $ will be removed
FOOBAR

1 The contextual quotes will be removed by the 📖heredoc function.
2 Different topic: Refer to the section 📖heredoc for the variable expansion inside a heredoc.


🔝 ◀️ Tokenizing

The 📖expanded input string will be tokenized. This means that the input string will be split into tokens. Each token will be marked with a type. The following token types exist:

enum e_token_type
{
	WORD_TOKEN,
	PIPE_TOKEN,
	RED_IN_TOKEN,
	RED_OUT_TOKEN,
};

To generate the tokens first the input string will be splited into an array. Therefore the NO_SPACE character is used as an seperator. Then a loop will go through the array and generate the tokens. The following table shows the token generation logic:

Logic Example
check for forbidden spaces between redir symbols < < file cat
if seperating characters in array entry (|, <, >, ', ") ls|wc
     if quote state == outside quotes ls|wc
       create token with value: everything until next seperator found token: ls
    else "ls|wc"
       create token with value: everything until matching quote found token: ls|wc
else ls
     create token with value: full array entry token: ls
✏️ Example echo "Hello" $USER "" '!' | wc -l     
Type Value
WORD_TOKEN echo
WORD_TOKEN Hello
WORD_TOKEN astein
WORD_TOKEN
WORD_TOKEN !
PIPE_TOKEN |
WORD_TOKEN wc
WORD_TOKEN -l

💡 Activate the 📖info mode to see the token list during runtime


🔝 ◀️ Parsing

After 📖tokienizing the input string, the tokens will be parsed into an AST. If the AST couldn't be build the input contains an syntax error (e.g. ls | | wc). If a vaild AST could be built, it will be used for the 📖execution.

Each node of the ast tree is an instance of the t_ast struct. It therefore has a type, a content and a two pointers to it's left and right child node.

This table shows all possible node types and their possible node conections:

Node Type Left Child Right Child
PIPE CMD PIPE CMD
CMD REDIR NULL ARG NULL
REDIR REDIR NULL NULL
ARG NULL ARG NULL

To build a vaild AST we use the following logic (BNF Notation):

 <job>          : <command> '|' <job>    
                | <command>              
                ;
 
 <command>      : <token list>           
				;
 
 <token list>   : [name]  <token list>   
                | [arg]   <token list>   
                | <redir> <token list>   
                ;
 
 <redir>        : <redir in> 
                | <redir out>
                ;
 
 <redir in>     : '<<' [file]
                | '<'  [file]
                ;
 
 <redir out>    : '>>' [file]
                | '>'  [file]
                ;
  			

✏️ Example echo "Hello" $USER "" '!' | wc -l     

Example

The following ast is visualiszed in a tree-like structure (left to right): Example

💡 Activate the 📖info mode to see the ast tree during runtime

Possible syntax errors could be:

frankenshell: syntax error: unclosed quotes
frankenshell: syntax error near unexpected token `<'
frankenshell: syntax error near unexpected token `>'
frankenshell: syntax error near unexpected token `|'
frankenshell: syntax error near unexpected token `newline'
frankenshell: syntax error near unexpected token `foo'

💡 The boolean syntax_err_encountered in the 📖t_mbox struct makes sure, that even if there are multiple errors, only the first error will be printed (like bash does).


🔝 ◀️ Executing

The execution of the ast is handled by the file 💻execute_ast.c.

The execution may contain the following steps. Checkout those sections for more details about the execution steps:

Note

  • Before the execution starts a vaild ast must be created. (📖Parsing)
    • If the ast couldn't be created the execution will be skipped.
  • The executor traverses the ast tree always from left to right.
    • This ensures that pipes and redirections will always be setup before any command is executed.
  • All commands are executed in a child process.
    • Exception: Single Builtin cmds
  • Pipes (|) allow the output of one command to be used as input for another, enabling command chaining.
  • All childs will be spawn right after each other (so before the previous child terminates).
    • Exception Heredoc: Before spawning a child process the parent process checks if the child includes a heredoc. In this case it waits until the child is finished. (like bash)
  • The next child process waits (because of the open pipe fd) until the previous child process is finished.
  • The parent waits for all childs to finish before it continues with the next cycle. Each time a child process is finished, the parent process updates the 📖exit status of the terminated child process.

The 🔗BPMN diagram below shows the main execution logic: execution logic


🔝 ◀️ Setup Command

Here the command will be setup. This means that a pipe* and a fork command will be executed.
The child process will then 📖setup the redirections and 📖run the command.

* Exception: Single or Last Commands don't need a pipe!

The 🔗BPMN diagram below shows the execution logic for seting up the commands: execution logic


🔝 ◀️ Setup Redirections

Redirections allow a flexible manipulation of command input and output, similar to standard bash functionality. The table below describes the redirection features available in frankenshell:

Feature Mode Description Example
< Input Redirection Redirects input from a file to a command. wc < file.txt
<< Heredoc Allows inputting multiple lines until a termination string is reached. << this_exits_the_hd wc
> Output Redirection Redirects the output of a command to a file, overwriting it if it already exists. echo "replace by foo" > file.txt
>> Append Output Redirection Appends command output to a file, creating it if it doesn't exist. echo "append foo" >> file.txt

Since there can be multiple redirections we need to keep track if one of them fails. Since bash only troughs one error.

Example:

$ < file_not_exists < file_not_exists_2 cat
bash: file_not_exists: No such file or directory

The 🔗BPMN diagram below shows the execution logic for seting up the redirections: execution logic


🔝 ◀️ Setup Heredoc

The heredoc redirection allows inputing multiple lines until a termination string (aka limiter) is read, an EOF Signal (CTRL + D)1, or an SIGINT Signal (CTRL + C)2 is received. The heredoc runs always in a child process and is connected to the command with a pipe. The heredoc uses readline to read the user input. Variable Expansion inside the heredoc is supported!3,4

1 frankenshell: warning: frankendoc at line 1 delimited by end-of-file (wanted `foo')
2 Checked via the global variable: g_signal_status == SIGNAL_EXIT_HD
3 Like in bash: The expansion is not supposed to work if the limiter contains contextual quotes! Therefore the 📖extract limiter part of the 📖variable expansion obtains '," and $ characters.
4 The heredoc can't be terminated if the expanded input matches the limiter! (first check matching limiter, then expand variable)

The 🔗BPMN diagram below shows the heredoc execution logic: execution logic


🔝 ◀️ Run Command

Since until now everything is already setup, the command can be executed.

📖Builtin commands just need to call the function pointer.

External / System commands need to be executed via execve. Therefore the command path is searched in the 📖environment variables. If the command is not found, the 📖exit status will be set to 127 and an error message will be printed. To call execve the command arguments and the enviomental variables need to be converted into an array of strings.

The 🔗BPMN diagram below shows the execution logic for running an command: execution logic


🔝 ◀️ Cleanup Cycle

After (successful or not) 📖executing the ast, all cycle realted variables will be freed. This includes:

  • input strings
  • tokens
  • ast

Also all open filedescriptors will be closed.


🔝 ◀️ Termination

All cycle realted variables will be freed on the end of each 📖each cycle. Therfore on termination of frankenshell only those variables (linked lists) need to be freed:


🔝 Known Bugs

export vs env

If the built-in export is called with a valid key but no equal sign (like export foo) foo should be listed in the output of the command export, but not in the output of the command env function call. In frankenshell, foo is not added to the linked list of variables and therefore is not printed in either case. A simple boolean in the s_env structure could solve this problem.


single cmds with redirs

Since frankenshell doesn't fork for a single builtin cmd like echo foo > out it changes the fds of the main process. This might affect the next cycle since the fds are not reseted.



🔝 Acknowledgments

Thx to those guys and gals for hints, tipps and feedback!

@Gabriel
@Martim
@Margarida
@Manuel


Buy Me A Coffee
If this documentation is helpful for you, feel free to buy me a coffee ;)


🔝 back to top 🔝