Let’s say you are working on a C program, which accepts a command as an argument or perhaps you just want to execute a shell command from your C program. In order to do so, you need to know the full path of the command.
Let’s dive in this.
Introduction
envp
The envp
variable is an array of strings that contains the environment variables for the current process. It is passed to the main
function as a parameter, along with argc
and argv
.
int main (int argc, char *argv[], char *envp[])
If we try to print the envp
variable, we can see that it contains a quite large list of environment variables:
SHELL=/bin/bash
QT_ACCESSIBILITY=1
COLORTERM=truecolor
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
NVM_INC=/home/vm_user/.nvm/versions/node/v22.5.1/include/node
XDG_MENU_PREFIX=gnome-
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
GNOME_SHELL_SESSION_MODE=ubuntu
MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
XMODIFIERS=@im=ibus
DESKTOP_SESSION=ubuntu
PATH=/home/vm_user/.local/bin:/home/vm_user/.bun/bin:/home/vm_user/.nvm/versions/node/v22.5.1/bin:/home/vm_user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin:/snap/bin:/home/vm_user/.local/bin:/usr/local/go/bin:/home/vm_user/.local/bin:/home/vm_user/.local/bin
The PATH
variable is a colon-separated list of directories that the shell searches for executable files. When you type a command in the terminal, the shell looks for that command in the directories listed in PATH
. If it finds the command, it executes it; otherwise, it returns an error.
For example, if you type ls
, the shell searches for the ls
executable in the directories listed in PATH
. It will split the PATH
variable into an array of strings using the colon (:
) as a delimiter, each representing a directory. Then, it will check each directory in order until it finds the ls
executable.
It will find the ls
command in /usr/bin/ls
directory and execute it.
Info
In order to find the full path of a command, you can use the
which
command in the terminal. For example,which ls
will return/usr/bin/ls
, which is the full path of thels
command 💡
Plan
As you may have guessed, we will be using the PATH
variable to find the full path of a command. We will split the PATH
variable into an array of strings using ft_split, each representing a directory. Then, we will check each directory using access()
until we find the executable file.
Implementation
Warning
- All code is norminette compliant, therefore may be slightly hard to grasp at first.
- We assume that libft is imported:
ft_strjoin
,ft_split
,ft_strncmp
is used in this code.
Start with the main
function
char *get_cmd_path(char *cmd, char **envp)
{
char *path_env; // The PATH variable
char **paths; // Splitted PATH variable
char *result; // The full path of the command
// ...
}
The get_cmd_path
accepts two parameters: the command name and the envp
variable. It will return the full path of the command if it exists, or NULL
if it doesn’t. Pretty intuitive here.
Find the PATH
in envp
First, let’s write a function that will loop through the envp
array to find the PATH
environment variable:
static void find_env_path(char **envp, char **path_env)
{
int j;
j = 0;
*path_env = NULL;
while (envp[j])
{
if (ft_strncmp(envp[j], "PATH=", 5) == 0)
{
*path_env = envp[j] + 5; // Skip "PATH="
break ;
}
j++;
}
}
This function will loop through the 2D envp
array and check if the string starts with PATH=
. If it does, it will set the path_env
variable to point to the value of the PATH
variable (i.e., the string after PATH=
).
Free 2D array
A simple function to free a 2D array of strings:
void free_2d(char **str)
{
int i;
i = 0;
while (str[i])
{
free(str[i]);
i++;
}
free(str);
}
Use find_env_path
and ft_split
Let’s return to the main get_cmd_path
function for a second, use find_env_path
and split the PATH
variable into an array of strings using ft_split
:
char *get_cmd_path(char *cmd, char **envp)
{
char *path_env; // The PATH variable
char **paths; // Splitted PATH variable
char *result; // The full path of the command
find_env_path(envp, &path_env);
if (!path_env)
return (NULL);
paths = ft_split(path_env, ':');
if (!paths)
return (NULL);
// ...
}
Find the command path
Now that we have the PATH
variable and split it into an array of strings, we can move forward to find the full path of the command.
The next function will:
- Split the
PATH
variable into an array of strings usingft_split
- Loop through the array of directories, append the command name to each directory, and check if the directory exists using
access()
- If it exists, return the full path of the command, otherwise return
NULL
Let’s name it find_cmd_path
:
// accepts the splitted PATH variable and the command name
static char *search_cmd_path(char **paths, char *cmd)
{
char *full_path;
char *tmp;
int i;
i = 0;
while (paths[i])
{
full_path = ft_strjoin(paths[i], "/");
if (!full_path)
break ;
tmp = ft_strjoin(full_path, cmd);
free(full_path);
if (!tmp)
break ;
if (access(tmp, X_OK) == 0)
{
// The command exists
free_2d(paths);
return (tmp);
}
// The command does not exist
free(tmp);
i++;
}
return (NULL);
}
Looks good!
Finalize get_cmd_path
Now, let’s finalize the get_cmd_path
function by calling search_cmd_path
and returning the result:
char *get_cmd_path(char *cmd, char **envp)
{
char *path_env;
char **paths;
char *result;
find_env_path(envp, &path_env);
if (!path_env)
return (NULL);
paths = ft_split(path_env, ':');
if (!paths)
return (NULL);
result = search_cmd_path(paths, cmd);
if (!result)
free_2d(paths);
return (result);
}
Now you can use the get_cmd_path
function to find the full path of a command in the Unix environment. For example:
#include <unistd.h>
int main(int argc, char **argv, char **envp)
{
char *cmd_path;
if (argc < 2)
{
write(2, "Usage: ./program <command>\n", 26);
return (1);
}
cmd_path = get_cmd_path(argv[1], envp);
if (cmd_path == NULL)
{
write(2, "Command not found\n", 18);
return (1);
}
// Using execve to execute the command
if (execve(cmd_path, &argv[1], envp) == -1)
{
write(2, "Error executing command\n", 24);
return (1);
}
return (0);
}
Code
void free_2d(char **str)
{
int i;
i = 0;
while (str[i])
{
free(str[i]);
i++;
}
free(str);
}
static void find_env_path(char **envp, char **path_env)
{
int j;
j = 0;
*path_env = NULL;
while (envp[j])
{
if (ft_strncmp(envp[j], "PATH=", 5) == 0)
{
*path_env = envp[j] + 5;
break ;
}
j++;
}
}
static char *search_cmd_path(char **paths, char *cmd)
{
char *full_path;
char *tmp;
int i;
i = 0;
while (paths[i])
{
full_path = ft_strjoin(paths[i], "/");
if (!full_path)
break ;
tmp = ft_strjoin(full_path, cmd);
free(full_path);
if (!tmp)
break ;
if (access(tmp, X_OK) == 0)
{
free_2d(paths);
return (tmp);
}
free(tmp);
i++;
}
return (NULL);
}
char *get_cmd_path(char *cmd, char **envp)
{
char **paths;
char *path_env;
char *result;
find_env_path(envp, &path_env);
if (!path_env)
return (NULL);
paths = ft_split(path_env, ':');
if (!paths)
return (NULL);
result = search_cmd_path(paths, cmd);
free_2d(paths);
return (result);
}
int main(int argc, char **argv, char **envp)
{
char *cmd_path;
if (argc < 2)
{
write(2, "Usage: ./program <command>\n", 26);
return (1);
}
cmd_path = get_cmd_path(argv[1], envp);
if (cmd_path == NULL)
{
write(2, "Command not found\n", 18);
return (1);
}
// Using execve to execute the command
if (execve(cmd_path, &argv[1], envp) == -1)
{
write(2, "Error executing command\n", 24);
return (1);
}
return (0);
}