C standard library provides functions system
and popen
to run a command. But is there a portable way to detect if a command exists?
For POSIX systems I found this to work very well (I'm checking for avconv in this example):
if (system("which avconv > /dev/null 2>&1")) {
// Command doesn't exist...
} else {
// Command does exist, do something with it...
}
The Redirect to /dev/null is simply to avoid anything being printed to stdout. It relies on the exit value from the which command alone.
which
- that is not necessary. In fact... IIANM, which
is not every required by POSIX to exist - correct me if I'm wrong. –
Cockle No, there isn't any standard C function for that.
The Unix-only solution is to split getenv("PATH")
on :
(colon), and try to find the command executable (with the stat
function) in the directories.
Some more details on what happens on Unix: execvp(3) in musl (a libc for Linux) getenv("PATH")
on :
(colon), and tries to run the program with the execve(2) system call (rather than the stat(2) system call). It's not feasible to emulate this 100% with stat(2), because for example execve(2) may fail with ENOEXEC (Exec format error) if it doesn't recognize the file format, which depends on kernel settings (including binfmt-misc module on Linux).
Some more details on what a Unix shell does. All the logic in the Dash shell is in exec.c. The shellexec function (called by the exec
builtin) is similar to execvp(3) in musl (split PATH
on :
+ execve(2)). The type
builtin calls the describe_command function, which calls find_command, which does: split PATH
on :
+ stat(2) + regular-file-check + execute-permission-check. The regular-file-check (near S_ISREG
) checks that the filename is a regular file (i.e. rather than a directory, pipe or device etc.). The execute-permission-check (near getegid
) checks approximately that the current process has execute permission on the file, by checking the relevant executable bit in st_mode
returned by stat(2). Please note that these checks are qlwo only approximations, the ENOEXEC error above also applies here.
os.pathsep
is ':'
on Unix and ';'
on Windows. Standard C and C++ don't predefine this constant. std::filesystem::path::preferred_separator
is something else ('/'
or '\\'
) in C++17. –
Archean While I don't think there is a completely portable way to do this (some systems don't even support command interpreters), system()
does return 0 if there were no errors while running your command. I suppose you could just try running your command and then check the return value of system.
To check if a command interpreter is available, call system( NULL )
and check for a non-zero value.
Here is a way to scan all the paths stored in the PATH
variable, scanning for the mathsat
executable:
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <iostream>
using namespace std;
int main ()
{
struct stat sb;
string delimiter = ":";
string path = string(getenv("PATH"));
size_t start_pos = 0, end_pos = 0;
while ((end_pos = path.find(':', start_pos)) != string::npos)
{
string current_path =
path.substr(start_pos, end_pos - start_pos) + "/mathsat";
if ((stat(mathsat_path.c_str(), &sb) == 0) && (sb.st_mode & S_IXOTH))
{
cout << "Okay" << endl;
return EXIT_SUCCESS;
}
start_pos = end_pos + 1;
}
return EXIT_SUCCESS;
}
There is no fully-portable way to do this in C, as @pts' answer explains. But if you assume a POSIX environment, you can do this.
First, let's recall how to check whether a path designates an executable file (with C and POSIX):
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int is_executable_file(char const * file_path)
{
struct stat sb;
return
(stat(file_path, &sb) == 0) &&
S_ISREG(sb.st_mode) &&
(access(file_path, X_OK) == 0);
}
Now let us use this function to check the paths obtained by concatenating the command name to all elements of the PATH environment variable:
#include <memory.h>
#include <stdlib.h>
#include <string.h>
int is_executable_file(char const * file_path);
char const * strchrnul_(char const * haystack, size_t haystack_length, char needle)
{
char const * strchr_result = strchr(haystack, needle);
return (strchr_result != NULL) ? strchr_result : haystack + haystack_length;
}
int executable_is_on_path(char const* executable_basename)
{
char const * const path_env_var = getenv("PATH");
// You may want to decide on your own policy regarding behavior when there is no PATH
if (path_env_var == NULL) { return is_executable(executable_basename); }
const size_t path_env_var_len = strlen(path_env_var);
if (path_env_var_len == 0) { return is_executable(executable_basename); }
const size_t basename_len = strlen(executable_basename);
// Allocate the maximum possible length of an executable path using a directory in the PATH string,
// so that we don't need to re-allocate later
char * executable_path = (char*) malloc(path_env_var_len + basename_len + 2);
if (executable_path == NULL) { return false; } // or you can throw an exception, or exit etc.
for (char const * token_start = path_env_var; token_start < path_env_var + path_env_var_len; ) {
static char const delimiter = ':';
char const *token_end = strchrnul_(token_start, path_env_var_len, delimiter);
// composing the full path of the prospective executable
{
off_t token_len = token_end - token_start;
strncpy(executable_path, token_start, token_len);
executable_path[token_len] = '/';
strcpy(executable_path + token_len + 1, executable_basename);
executable_path[basename_len + token_len + 1] = '\0';
}
if (is_executable(executable_path)) {
free(executable_path);
return true;
}
token_start = token_end + 1;
}
free(executable_path);
return false;
}
If you were using C++, you would have a somewhat easier time with string, filesystem and path operations; but this is pure C (in fact, it's C89).
A variation of what Ben Collins said, but using POSIX command -v
instead of which
which is not part of POSIX.
You can do this by running the command:
command -v CMD_TO_CHECK > /dev/null 2>&1
- The
-v
flags tellscommand
to not execute the file and would normally also print the file path tostdout
. - The
> /dev/null
redirectsstdout
output to a null stream, meaning it is just discarded. This way we won't have clutter in our program'sstdout
. - The
2>&1
mergesstderr
intostdout
by redirecting the file descriptor2
's output into1
's, meaningstderr
won't be printed either. On my systemcommand -v
doesn't ever seem to print tostderr
but I don't know if this would be true for every implementation.
If the program is found, system
returns 0
, otherwise it will return something greater than 0
if the program doesn't exist. This is why when checking, you should use system(...) == 0
, or alternatively !system(...)
.
Example to check if Clang exists:
#include <stdlib.h>
// ...
if( !system("command -v clang > /dev/null 2>&1") )
{
// The command exists!
}
else
{
// Doesn't exist
}
Using this you can make a function to check if an arbitrary program exists:
#include <stdio.h>
#include <stdlib.h>
int programExists(const char *name)
{
char buffer[512];
snprintf(
buffer,
sizeof buffer,
"command -v %s > /dev/null 2>&1",
name
);
return !system( buffer );
}
int main()
{
printf("GCC ");
if( programExists("gcc") ) printf("exists!\n");
else printf("not found.\n");
printf("obscureprogram ");
if( programExists("obscureprogram") ) printf("exists!\n");
else printf("not found.\n");
return 0;
}
or use the first example to avoid the buffering and using snprintf
.
here is a snippet from my code that i use to look for existing commands in the system using PATH
from environment variables.
first i get the PATH
variable then i split it using :
then i store it in 2d array and return in get_path(...)
.
after that i join the string cmd with each path in PATH variable and check if i can access it using access(2)
function if the file exist then i found the path to the executable and i return it else i print error with perror()
and return NULL.
char **get_paths(char **envp)
{
while (*envp)
{
if (ft_strncmp(*envp, "PATH=", 5) == 0)
return (ft_split(*envp + 5, ':'));
envp++;
}
return (NULL);
}
char *cmd_file(char **envp, char *cmd)
{
char **paths;
char *path_tmp;
char *file;
paths = envp;
if (access(cmd, X_OK) == 0)
return (ft_strdup(cmd));
while (*paths && cmd)
{
path_tmp = ft_strjoin(*paths, "/");
file = ft_strjoin(path_tmp, cmd);
free(path_tmp);
if (access(file, X_OK) == 0)
return (file);
free(file);
paths++;
}
perror(cmd);
return (NULL);
}
int main(int argc, char **argv, char **envp)
{
char **paths;
char *cmd;
paths = get_paths(envp);
cmd = cmd_file(paths, argv[1]);
printf("%s\n", cmd);
}
ft_whatever
. –
Cockle © 2022 - 2024 — McMap. All rights reserved.