2

我想测试运行我的程序的系统上是否安装了 GNUPlot。
为此,我想我将通过 stat() 调用测试用户安装位置中是否存在 gnuplot 可执行文件。

但是,我不知道如何在 C 中读取 $PATH 环境变量,因此我可以测试这些位置中是否存在文件。

4

7 回答 7

6

使用该getenv()功能。

char *paths = getenv("PATH");

要遍历以列分隔的路径列表的各个部分,请使用strchr()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *dup = strdup(getenv("PATH"));
char *s = dup;
char *p = NULL;
do {
    p = strchr(s, ':');
    if (p != NULL) {
        p[0] = 0;
    }
    printf("Path in $PATH: %s\n", s);
    s = p + 1;
} while (p != NULL);

free(dup);
于 2013-01-28T21:07:12.957 回答
2

用于getenv()检查特定环境变量的值。

于 2013-01-28T21:06:09.043 回答
1

要读取PATH环境变量,请使用getenv("PATH").

但是,如果您只想在可用时运行,gnuplot如果不可用则执行一些回退操作,那么您应该尝试运行它(例如使用forkandexecvpposix_spawnp)并处理失败情况。

于 2013-01-28T21:07:06.147 回答
0

让哪个为你工作

if (system("which gnuplot"))
    /* not installed or not in path or not executable or some other error */

如果出于某种原因需要完整路径,请使用 popen 运行 which。
或者使用一些标志运行 gnuplot,使其立即返回 0 */

if (system("gnuplot --version"))
    /* not installed ... */
于 2013-01-29T03:18:24.200 回答
0

我有类似的需求,并通过复制 libc execvp 代码源解决了它。我在我能想到的最跨平台上做了(我没有保证,只在 linux 上测试过)。如果这对你来说不是这样,并且你关心性能,你应该使用 acess 或 _acess。请注意,没有任何错误检查,它只会返回 NULL 或在路径中创建的可打开文件。

接受的答案有时是不可接受的,当您愿意一遍又一遍地运行相同的小二进制文件时,每次通过调用 execvp 重做路径搜索可能是不可忽略的开销。

所以这里是代码和相关的测试,你会主要对这个search_in_path_openable_file功能感兴趣。

.h 文件:

bool is_openable_file(char* path);
/*Return true if path is a readable file. You can call perror if return false to check what happened*/

char* search_in_path_openable_file(char* file_name);
/*Search into PATH env variable a file_name and return the full path of the first that is openable, NULL if not in path*/

char* search_executable(char* file_name);
/*Search file, if not openable and not absolute path(contain /), look for opennable file in the path. If nothing is openable, return NULL. If something is openable, return it as it is (not guaratented to have a full path, but garatanted to be openable)*/

.c 文件:

#include "file_info.h"
#include <stdio.h>
#include <string.h> //strcpy

/*I wanted to do a really cross platform way. access or _acess may be better*/
bool is_openable_file(char *path) {
    FILE *fp = fopen(path, "r");
    if (fp) {
        // exists
        fclose(fp);
        return true;
    }

    return false;
}

bool is_openable_file_until(char *path_begin, size_t until) {
    char old = path_begin[until];
    path_begin[until] = 0;
    bool res = is_openable_file(path_begin);
    path_begin[until] = old;
    return res;
}

/*You may thinks that libc would have done this function and use it to implement execp function family, but you would be wrong. They just hardcoded the search in every execp function. Unbelievable.
 *
 * So this function is a modification of their execvp function. 
 *
 * */

char* search_in_path_openable_file(char* file){
    char *path = getenv("PATH");
    if (path == NULL)
        return NULL;
    size_t pathlen = strlen(path);
    size_t len = strlen(file) + 1;
    int total_max_size=pathlen + len;
    char* buf=malloc(sizeof(char)*total_max_size);
    if (*file == '\0') {
        return NULL;
    }
    char *name, *p;
    /* Copy the file name at the top.  */
    name = memcpy(buf + pathlen + 1, file, len);
    /* And add the slash.  */
    *--name = '/';
    p = path;
    do {
        char *startp;
        path = p;
        //Let's avoid this GNU extension.
        //p = strchrnul (path, ':');
        p = strchr(path, ':');
        if (!p)
            p = strchr(path, '\0');
        if (p == path)
            /* Two adjacent colons, or a colon at the beginning or the end
               of `PATH' means to search the current directory.  */
            startp = name + 1;
        else
            startp = memcpy(name - (p - path), path, p - path);
        /* Try to execute this name.  If it works, execv will not return.  */
        if (is_openable_file(startp))
            return startp;
    } while (*p++ != '\0');
    /* We tried every element and none of them worked.  */
    return NULL;
}

char* search_executable(char* file_name){

    if (is_openable_file(file_name)){//See realpath manual bug. Watch out
        return file_name;
    }
    if (strchr (file_name, '/') != NULL) //Don't search when it contains a slash. 
        return NULL;
    return search_in_path_openable_file(file_name);
}

测试(如您所见,我没有对这个功能进行很多测试,可能存在一些问题,使用风险自负):

 #include "file_info.h"
#include "munit.h"
#include <stdbool.h>
#include <unistd.h>
static void generate_search_executable(char* test_str, char* expected){
    char* res= search_executable(test_str);
     if (res==NULL)
        munit_assert_ptr(expected,==,NULL );
     else
    munit_assert_string_equal(expected,res);
}

static void generate_openable(char* test_str, bool expected){
    bool res= is_openable_file(test_str);
    munit_assert_true(expected==res);
}

static void generate_path_search(char* test_str, char* expected_res){
     char* res= search_in_path_openable_file(test_str);
     if (res==NULL)
        munit_assert_ptr(expected_res,==,NULL );
     else
    munit_assert_string_equal(expected_res,res);
}

//TODO do for other platform, better test would also set path to a custom folder that we control
#define EXISTING_FILE_NOT_IN_PATH "/usr/include/stdlib.h" 
#define EXISTING_FILE_IN_PATH "ls" 
#define EXISTING_FILE_IN_PATH_FULL "/bin/ls" 
#define NOT_EXISTING_FILE "/usrarfzsvdvwxv/ixvxwvnxcvcelgude/ssdvtdbool.h" 
int main() {

    generate_openable(EXISTING_FILE_IN_PATH, false);
    generate_openable(EXISTING_FILE_NOT_IN_PATH, true);
    generate_openable(NOT_EXISTING_FILE, false);

    generate_path_search(EXISTING_FILE_IN_PATH, EXISTING_FILE_IN_PATH_FULL);
    generate_path_search(NOT_EXISTING_FILE, NULL);
    generate_path_search(EXISTING_FILE_NOT_IN_PATH, NULL);

    generate_search_executable(EXISTING_FILE_IN_PATH, EXISTING_FILE_IN_PATH_FULL);
    generate_search_executable(NOT_EXISTING_FILE, NULL);
    generate_search_executable(EXISTING_FILE_NOT_IN_PATH, EXISTING_FILE_NOT_IN_PATH);

    generate_search_executable("", NULL );

    //test current folder existence(maybe it just depend on path containing .,I am not sure, in that case we should remove thoses tests
    generate_search_executable("file_info_test", "file_info_test" );


}
于 2020-06-09T15:02:45.327 回答
0

在前面的答案之一的基础上构建,您可以使用getenv获取内容PATH然后迭代其组件。而不是使用strchr你可以使用strsep

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>

bool exists(const char fname[])
{
    return access(fname, F_OK | X_OK) != -1;
}

bool find_in_path(const char name[], char *fullpath, size_t sz) {
    char *paths = strdup(getenv("PATH"));
    char *tmp = paths; // to use in free
    const char *item;
    bool found = false;
    while ((item = strsep(&paths, ":")) != NULL) {
        snprintf(fullpath, sz, "%s/%s", item, name);
        if (exists(fullpath)) {
            found = true;
            break;
        }
    }
    free(tmp);
    return found;
}

int main() {
    char fullpath[512];
    bool found = find_in_path("uname", fullpath, sizeof(fullpath));
    if (found) {
        printf("found: %s\n", fullpath);
    }
    return 0;
}
于 2021-02-10T15:33:57.097 回答
0

使用 C++17 获取路径元素的向量。

% a.out ls
/bin/ls

#include <iostream>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
using namespace std;

vector<string> get_paths (string str)
{
   vector<string> result;
   while(!str.empty())
   {
      if (auto pos { str.find_first_of (':') }; pos == string::npos)
      {
         result.push_back(str);
         break;
      }
      else
      {
         result.emplace_back(str.substr(0, pos));
         str.erase(0, pos + 1);
      }
   }
   return move(result);
}

bool exist(const string& fname, int perm=F_OK) { return access(fname.c_str(), perm) == 0; }

int main (int argc, char *argv[])
{
   auto result { get_paths(getenv("PATH")) };
   for (auto pp : result)
   {
      string npath { pp };
      if (*npath.rbegin() != '/')
         npath += '/';
      npath += argv[1];
      if (exist(npath))
         cout << npath << endl;
   }
   return 0;
}
于 2021-02-22T06:31:33.283 回答