查找命令是否存在的便携式方法(C/C++)

问题描述 投票:0回答:7

C 标准库提供了函数

system
popen
来运行命令。但是有没有一种可移植的方法来检测命令是否存在?

c path system executable popen
7个回答
15
投票

对于 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命令的退出值。


6
投票

不,没有任何标准的 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 错误也适用于此。


5
投票

虽然我认为没有完全可移植的方法来执行此操作(某些系统甚至不支持命令解释器),但如果运行命令时没有错误,

system()
确实会返回 0。我想你可以尝试运行你的命令,然后检查系统的返回值。

要检查命令解释器是否可用,请调用

system( NULL )
并检查非零值。


5
投票

这里有一种扫描

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;
}

0
投票

正如 @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)。


0
投票

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


-1
投票

这是我的代码中的一个片段,我用它来使用环境变量中的

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.