C 标准库提供了函数
system
和 popen
来运行命令。但是有没有一种可移植的方法来检测命令是否存在?
对于 POSIX 系统,我发现这非常有效(我在本例中检查 avconv):
if (system("which avconv > /dev/null 2>&1")) {
// Command doesn't exist...
} else {
// Command does exist, do something with it...
}
重定向到 /dev/null 只是为了避免将任何内容打印到标准输出。它仅依赖于which命令的退出值。
不,没有任何标准的 C 函数。
仅限 Unix 的解决方案是将
getenv("PATH")
拆分为 :
(冒号),并尝试在目录中查找可执行命令(使用 stat
函数)。
有关 Unix 上发生的情况的更多详细信息: musl(Linux 的 libc)中的 execvp(3)
getenv("PATH")
(冒号)上的 :
,并尝试使用 execve(2) 系统调用运行程序(而不是 stat(2) 系统调用)。使用 stat(2) 模拟 100% 是不可行的,因为例如 execve(2) 如果无法识别文件格式,可能会因 ENOEXEC(Exec 格式错误)而失败,这取决于内核设置(包括 binfmt- Linux 上的 Misc 模块)。
有关 Unix shell 功能的更多详细信息。 Dash shell 中的所有逻辑都在 exec.c 中。 shellexec 函数(由
exec
内置函数调用)类似于 musl 中的 execvp(3)(在 PATH
+ execve(2) 上拆分 :
)。 type
内置函数调用 describe_command 函数,该函数又调用 find_command,它执行以下操作:在 PATH
+ stat(2) + 常规文件检查 + 执行权限检查上拆分 :
。常规文件检查(靠近 S_ISREG
)检查文件名是否是常规文件(即而不是目录、管道或设备等)。执行权限检查(靠近 getegid
)通过检查 stat(2) 返回的 st_mode
中的相关可执行位来大致检查当前进程是否具有文件的执行权限。请注意,这些检查只是 qlwo 的近似值,上面的 ENOEXEC 错误也适用于此。
虽然我认为没有完全可移植的方法来执行此操作(某些系统甚至不支持命令解释器),但如果运行命令时没有错误,
system()
确实会返回 0。我想你可以尝试运行你的命令,然后检查系统的返回值。
要检查命令解释器是否可用,请调用
system( NULL )
并检查非零值。
这里有一种扫描
PATH
变量中存储的所有路径的方法,扫描mathsat
可执行文件:
#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;
}
正如 @pts' answer 所解释的那样,在 C 中没有完全可移植的方法来执行此操作。但如果您假设有一个 POSIX 环境,您就可以做到这一点。
首先,让我们回忆一下如何检查路径是否指定可执行文件(使用C和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);
}
现在让我们使用这个函数来检查将命令名连接到 PATH 环境变量的所有元素所获得的路径:
#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;
}
如果你使用 C++,你会更容易处理字符串、文件系统和路径操作;但这是纯C(事实上,它是C89)。
Ben Collins 所说的一种变体,但使用 POSIX
command -v
而不是 which
,后者不是 POSIX 的一部分。
您可以通过运行以下命令来完成此操作:
command -v CMD_TO_CHECK > /dev/null 2>&1
-v
标志告诉command
不要执行该文件,并且通常还会打印到stdout
的文件路径。> /dev/null
将stdout
输出重定向到空流,这意味着它只是被丢弃。这样我们的程序中就不会有混乱的stdout
。2>&1
通过将文件描述符stderr
的输出重定向到stdout
的输出来将2
合并到1
中,这意味着stderr
也不会被打印。在我的系统上 command -v
似乎永远不会打印到 stderr
但我不知道这是否适用于每个实现。检查 Clang 是否存在的示例:
#include <stdlib.h>
// ...
if( !system("command -v clang > /dev/null 2>&1") )
{
// The command exists!
}
else
{
// Doesn't exist
}
使用它,您可以创建一个函数来检查任意程序是否存在:
#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;
}
或使用第一个示例来避免缓冲并使用
snprintf
。
这是我的代码中的一个片段,我用它来使用环境变量中的
PATH
查找系统中的现有命令。
首先我得到
PATH
变量,然后使用 :
拆分它,然后将其存储在 2d 数组中并返回 get_path(...)
。
之后,我将字符串 cmd 与 PATH 变量中的每个路径连接起来,并检查是否可以使用
access(2)
函数访问它(如果文件存在),然后我找到了可执行文件的路径,然后返回它,否则我用 perror()
打印错误并返回 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);
}