Building my own Shell in C
Programmierung einer Shell in C
The correct handling of quotes is an important feature of a shell. For this, I implemented support for single and double quotes, as well as
an escape character. The backslash "\" is used to tell the shell, that the following character should not be interpreted with its special meaning.
With this, it is possible to use a command such as echo \'hello\' and receive the output 'hello' instead of just hello without the quotes.
Der korrekte Umgang mit Anführungszeichen ist eine zentrale Funktion einer Shell. Daher habe ich die Unterstützung für einfache und doppelte
Anführungszeichen sowie für ein Escape-Zeichen implementiert. Der Backslash "\" signalisiert der Shell, dass das unmittelbar folgende Zeichen nicht
mit seiner speziellen Bedeutung interpretiert werden soll. Dadurch ist es beispielsweise möglich, den Befehl echo 'hello' zu verwenden und als Ausgabe
'hello' zu erhalten, anstatt hello ohne Anführungszeichen.
Standard output (stdout) is the standard data stream that programs use to send data to the terminal or the user. In this context, there is also
standard input (stdin) for input and standard error (stderr) for errors messages. To support operators such as ">" or ">>" for redirection,
I use dup2 to replace the stdout file descriptor with a file descriptor that refers to a file on disk. For the connection between
a file and a file descriptor, I use the open function and set appropriate flags to enable either truncation or appending.
Die Standardausgabe (stdout) ist der Datenstrom, über den Programme ihre Ausgaben an das Terminal oder den Benutzer senden. Ergänzend dazu
existieren die Standardeingabe (stdin) für Eingaben sowie die Standardfehlerausgabe (stderr) für Fehlermeldungen. Zur Unterstützung von
Umleitungsoperatoren wie ">" oder ">>" verwende ich dup2, um den stdout-Dateideskriptor durch einen Dateideskriptor zu ersetzen, der auf eine Datei
im Dateisystem verweist. Die Verknüpfung zwischen Datei und Dateideskriptor erfolgt über die Funktion open, wobei ich die entsprechenden Flags setze,
um entweder das Überschreiben oder Anhängen an die Datei zu realisieren.
The next part is to support autocompletion for builtins and executables when pressing the TAB key. The inital approach used fgets to read user
input into a buffer. To avoid implementing autocompletion from scratch, I replaced this with the readline library. To obtain a list of executables
from the PATH variable, I implemented dynamic memory allocation using malloc and realloc. Finally, I created a custom name generator function
for readline to provide the detected executables and builtins for completion.
Im nächsten Schritt implementiere ich die Autovervollständigung für Builtins und ausführbare Programme beim Drücken der TAB-Taste.
Ursprünglich wurde fgets verwendet, um die Benutzereingaben in einen Buffer einzulesen. Um die Autovervollständigung nicht vollständig selbst entwickeln
zu müssen, verwende ich die readline Library. Zur Ermittlung der ausführbaren Programme aus der PATH-Variable setze ich dynamische Speicherverwaltung
mittels malloc und realloc ein. Abschließend implementiere ich eine benutzerdefinierte Generatorfunktion für readline, die die erkannten Builtins und
ausführbaren Programme für die Vervollständigung bereitstellt.
Pipelines are used to connect the standard output of one command to the standard input of the next command. To create a pipe the "|" operator is
used, so I first check whether it is present in the input. Next, I create a pipe file descriptor and call the pipe function to setup the reader
and writer ends. This is done by splitting the command string at "|" into seperate parts to obtain the individual commands. Afterward, I run the
already implemented command-handling logic for each command and apply the appropiate pipe redirection.
Pipelines verbinden die Standardausgabe eines Befehls mit der Standardeingabe des darauffolgenden Befehls. Um eine Pipe zu erstellen, wird der Operator
"|" verwendet. Daher prüfe ich zunächst, ob dieser in der Eingabe vorhanden ist. Anschließend erstelle ich ein Pipe-Dateideskriptorpaar und rufe die
Funktion pipe auf, um das Lesende und Schreibende Ende der Pipe einzurichten. Dazu wird der Command am Operator "|" in einzelne Teilbefehle zerlegt.
Für jeden dieser Befehle führe ich anschließend die bereits implementierte Befehlsverarbeitungslogik aus und richte die Pipe-Umleitungen ein.
Another useful shell feature is command history, which is also required for features such as reverse-search. The history command itself is implemented
as a shell builtin that stores previously executed commands in a buffer. The GNU History Library provides functions such as add_history for list management,
which I use in my shell. Because i use the readline library, navigation with the up and down arrow keys works without additional implementation.
Finally, I implement the "-r" "-w" "-a" flags to read, write, and append the stored history buffer to a file. With this final step, the shell is
fully functional and all tests pass, as demonstrated in the demo video.
Eine weitere zentrale Funktion einer Shell ist die Befehls-Historie, die unter anderem die Reverse Search ermöglicht. Der history-Befehl wird als Builtin
implementiert und speichert zuvor ausgeführte Befehle in einem internen Buffer. Die GNU History Library stellt Funktionen wie add_history zur Verwaltung
der Historienliste bereit, die ich für meine Shell nutze. Durch die Verwendung der readline-Bibliothek funktioniert zudem die Navigation durch die
Befehlshistorie mittels der Pfeiltasten nach oben und unten ohne zusätzlichen Aufwand. Abschließend implementiere ich die Optionen "-r", "-w" und "-a"
um den gespeicherten Verlauf aus einer Datei zu lesen, in eine Datei zu schreiben oder anzuhängen. Mit diesem letzten Schritt ist die Shell vollständig
funktionsfähig und besteht alle Tests, wie im Demo-Video gezeigt wird.