Subscribe to my newsletter and never miss my upcoming articles
Up until the end of Part 3 of the series, our shell was able to show a prompt
for the end user to type some commands, read
, parse
and execute
the command, except that the user had to explicitly provide the path for each command they wanted to execute.
In this part, we want to focus on generating the path for each command ourselves.
Here is what our simple shell looks like so far in terms of files created and the codes in them. We have 3 files so far (execmd.c
, main.c
, and main.h
).
For execmd.c
, here are the codes:
#include "main.h"
void execmd(char **argv){
char *command = NULL;
if (argv){
command = argv[0];
if (execve(command, argv, NULL) == -1){
perror("Error:");
}
}
}
For main.h
, here are the codes in it:
#include
#include
#include
#include
void execmd(char **argv);
For main.c
, here are the codes in it:
#include "main.h"
int main(int ac, char **argv)
{
char *prompt = "(Eshell) $ ";
char *lineptr = NULL, *lineptr_copy = NULL;
size_t n = 0;
ssize_t nchars_read;
const char *delim = " n";
int num_tokens = 0;
char *token;
int i;
(void)ac;
while (1)
{
printf("%s", prompt);
nchars_read = getline(&lineptr, &n, stdin);
if (nchars_read == -1)
{
printf("Exiting shell....n");
return (-1);
}
lineptr_copy = malloc(sizeof(char) * nchars_read);
if (lineptr_copy == NULL)
{
perror("tsh: memory allocation error");
return (-1);
}
strcpy(lineptr_copy, lineptr);
token = strtok(lineptr, delim);
while (token != NULL)
{
num_tokens++;
token = strtok(NULL, delim);
}
num_tokens++;
argv = malloc(sizeof(char *) * num_tokens);
token = strtok(lineptr_copy, delim);
for (i = 0; token != NULL; i++)
{
argv[i] = malloc(sizeof(char) * strlen(token));
strcpy(argv[i], token);
token = strtok(NULL, delim);
}
argv[i] = NULL;
execmd(argv);
}
free(lineptr_copy);
free(lineptr);
return (0);
}
To help us generate the path for each command that is typed, we will create a separate file to hold our function that will do that job. If you are familiar with the Linux environment, then you can say that this function we are about to create works like the which
Linux command.
Let’s call our function get_location.c
. So, go ahead and create a new file and name it as such.
How to create the get_location
function
This function is expected to take in the command that was passed (eg: ls
) and return the path of that command (eg: /usr/bin/ls
). The prototype of this function will therefore have a char *
as both the return data type and the parameter/argument since both are strings.
char *get_location(char *command);
will be the prototype for our function. Go ahead and add this to the main.h
file.
#include
#include
#include
#include
void execmd(char **argv);
char *get_location(char *command);
To help us get the path for the given command, we first have to access the environment variable PATH and it’s value. The value of this PATH variable, as explained in part 3 of this series is a string with all the various paths that your shell searches through by default where each path is separated by a colon (:
).
In a Linux terminal, you can check this PATH variable by typing the command echo $PATH
. When I type this on my terminal, this is the output and yours may be very similar.
PATH=/home/ehoneahobed/.local/bin:/home/ehoneahobed/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
In the C language, there is a function that allows us to get the value for this environment variable. Let us start off by creating a variable to which we will assign the path obtained from the environment variables from.
We will call our variable path
and along the line we will need to keep a copy of it (I will explain why soon) so we will also create a path_copy
variable.
Our get_location.c
file should therefore start like this:
#include "main.h"
char *get_location(char *command){
char *path, *path_copy;
}
How to get the value of the PATH
environment variable in C
The function that we can use to get the PATH environment variable is getenv()
. As we always do, let’s check the man page for this function and see how we can use it.
man getenv
Per the description from the man page, this function searches the environment list to find the specific environment variable name which you pass to it as argument. The prototype for the function is char *getenv(const char *name)
and it returns a pointer to the value in the environment, or NULL if there is no match.
In our case, the variable we are targeting is PATH
so we can write the function as such
getenv("PATH")
But we already created our own variable path
that we will be assigning the return value of getenv()
to. So it finally looks like this when added to our get_location()
function.
#include "main.h"
char *get_location(char *command){
char *path, *path_copy;
path = getenv("PATH");
}
Create a duplicate of the path
I mentioned this earlier but haven’t explain why we will need a duplicate of the path. To finally help us get the path for each command, we will be using the strtok
function and if you remember anything about that function, it should be its destructive nature. Strtok breaks down a string into its component words or collection of characters based on a specified delimiter.
With the help of the strdup()
function, we can easily create the new copy of path
. If you are not familiar with strdup()
, go ahead and check the man page for it (man strdup
). This function will dynamically allocate memory for you so remember to free that memory when you are done using your variable holding its return value (path_copy
in our case).
path_copy = strdup(path);
How to generate the path for the given command
In order to generate the path for the given command, we will need to follow the steps below:
- Get the length of the command given
- Break down the path into individual tokens
- Run a loop in which we append a forward slash (
/
) followed by thecommand
then a NULL terminating character (