- Learn how shells create new child processes and connect the I/O to the terminal.
- Gain a better understanding of the
fork()function wrapper. - Learn to correctly execute commands written by the user and treat errors.
A shell is a command-line interpreter that provides a text-based user interface for operating systems. Bash is both an interactive command language and a scripting language. It is used to interact with the file system, applications, operating system and more.
For this assignment you will build a Bash-like shell with minimal functionalities like traversing the file system, running applications, redirecting their output or piping the output from one application into the input of another. The details of the functionalities that must be implemented will be further explained.
The shell will support a built-in command for navigating the file system, called cd.
To implement this feature you will need to store the current directory path because the user can provide either relative or absolute paths as arguments to the cd command.
The built-in pwd command will show the current directory path.
Check the following examples below to understand these functionalities.
> pwd
/home/student
> cd operating-systems/assignments/minishell
> pwd
/home/student/operating-systems/assignments/minishell
> cd inexitent
no such file or directory
> cd /usr/lib
> pwd
/usr/libNOTE: Using the
cdcommand without any arguments or with more than one argument doesn't affect the current directory path. Make sure this edge case is handled in a way that prevents crashes.BONUS 1: You can implement
cd(without parameters) which will change the current directory to$HOMEvariable, if defined.BONUS 2: You can implement
cd -which will change the current directory to$OLDPWDvariable, if defined.
Inputting either quit or exit should close the minishell.
Suppose you have an executable named sum in the current directory.
It takes arbitrarily many numbers as arguments and prints their sum to stdout.
The following example shows how the minishell implemented by you should behave.
> ./sum 2 4 1
7If the executable is located at the /home/student/sum absolute path, the following example should also be valid.
> /home/student/sum 2 4 1
7Each application will run in a separate child process of the minishell created using fork.
Your shell will support using environment variables.
The environment variables will be initially inherited from the bash process that started your minishell application.
If an undefined variable is used, its value is the empty string: "".
NOTE: The following examples contain comments which don't need to be supported by the minishell. They are present here only to give a better understanding of the minishell's functionalities.
> NAME="John Doe" # Will assign the value "John Doe" to the NAME variable
> AGE=27 # Will assign the value 27 to the AGE variable
> ./identify $NAME $LOCATION $AGE # Will translate to ./identify "John Doe" "" 27 because $LOCATION is not definedA variable can be assigned to another variable.
> OLD_NAME=$NAME # Will assign the value of the NAME variable to OLD_NAMEBy using the ; operator, you can chain multiple commands that will run sequentially, one after another.
In the command expr1; expr2 it is guaranteed that expr1 will finish before expr2 is be evaluated.
> echo "Hello"; echo "world!"; echo "Bye!"
Hello
world!
Bye!By using the & operator you can chain multiple commands that will run in parallel.
When running the command expr1 & expr2, both expressions are evaluated at the same time (by different processes).
The order in which the two commands finish is not guaranteed.
> echo "Hello" & echo "world!" & echo "Bye!" # The words may be printed in any order
world!
Bye!
HelloWith the | operator you can chain multiple commands so that the standard output of the first command is redirected to the standard input of the second command.
Hint: Look into anonymous pipes and file descriptor inheritance while using fork.
> echo "Bye" # command outputs "Bye"
Bye
> ./reverse_input
Hello # command reads input "Hello"
olleH # outputs the reversed string "olleH"
> echo "world" | ./reverse_input # the output generated by the echo command will be used as input for the reverse_input executable
dlrowThe && operator allows chaining commands that are executed sequentially, from left to right.
The chain of execution stops at the first command that exits with an error (return code not 0).
# throw_error always exits with a return code different than 0 and outputs to stderr "ERROR: I always fail"
> echo "H" && echo "e" && echo "l" && ./throw_error && echo "l" && echo "o"
H
e
l
ERROR: I always failThe || operator allows chaining commands that are executed sequentially, from left to right.
The chain of execution stops at the first command that exits successfully (return code is 0).
# throw_error always exits with a return code different than 0 and outputs to stderr "ERROR: I always fail"
> ./throw_error || ./throw_error || echo "Hello" || echo "world!" || echo "Bye!"
ERROR: I always fail
ERROR: I always fail
HelloThe priority of the available operators is the following. The lower the number, the higher the priority:
- Pipe operator (
|) - Conditional execution operators (
&&or||) - Parallel operator (
&) - Sequential operator (
;)
The shell must support the following redirection options:
< filename- redirectsfilenameto standard input> filename- redirects standard output tofilename2> filename- redirects standard error tofilename&> filename- redirects standard output and standard error tofilename>> filename- redirects standard output tofilenamein append mode2>> filename- redirects standard error tofilenamein append mode
Hint: Look into open, dup2 and close.
The testing is automated.
Tests are located in the inputs/ directory.
student@os:~/.../assignments/minishell/checker/_test/inputs$ ls -F
test_01.txt test_03.txt test_05.txt test_07.txt test_09.txt test_11.txt test_13.txt test_15.txt test_17.txt
test_02.txt test_04.txt test_06.txt test_08.txt test_10.txt test_12.txt test_14.txt test_16.txt test_18.txtTo execute tests you need to run:
student@os:~/.../assignments/minishell/checker$ ./run_all.shTo inspect the differences between the output of the mini-shell and the reference binary set DO_CLEANUP=no in _test/run_test.sh.
To see the results of the tests, you can check _test/outputs/ directory.
To inspect the unreleased resources (memory leaks, file descriptors) set USE_VALGRIND=yes and DO_CLEANUP=no in _test/run_test.sh.
You can modify both the path to the Valgrind log file and the command parameters.
To see the results of the tests, you can check _test/outputs/ directory.
checkpatch.pl is a script used in the development of the Linux kernel.
It is used to check patches that are submitted to the kernel mailing list for adherence to the coding style guidelines of the Linux kernel.
The script checks the code for common coding style issues, such as indentation, spacing, line length, function and variable naming conventions, and other formatting rules. It also checks for some common errors, such as uninitialized variables, memory leaks, and other potential bugs.
You can download the checkpatch.pl script from the official Linux kernel repository.
Running the following command will show you linting warnings and errors:
./checkpatch.pl --no-tree --terse -f /path/to/your/code.c