【问题标题】:Search for a file in $PATH on Linux in C在 C 中的 Linux 上的 $PATH 中搜索文件
【发布时间】:2013-01-28 21:04:36
【问题描述】:

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

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

【问题讨论】:

  • 嘿@darnir,您发布了一个可能与许多应用程序相关的问题。你似乎已经解决了这个问题。您能否将您的代码分享给所有想要做您所做的事情的人?

标签: c linux environment-variables


【解决方案1】:

使用getenv() function.

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 然后我将不得不正则表达式解析列表并统计每个路径?还是有更好的办法?
    • @darnir 这太过分了。只需使用strchr(paths, ':') 循环遍历它。
    • 不错。谢谢H2CO3。你刚刚为我节省了很多时间!我真的需要开始编写更多的 C 代码了。
    【解决方案3】:

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

    但是,如果您只想在gnuplot 可用时运行它,并在它不可用时执行一些后备操作,那么您应该尝试运行它(例如使用fork 和@ 987654325@或posix_spawnp),并处理失败案例。

    【讨论】:

    • 我正在使用通过管道执行它的遗留代码。用fork重写太费劲了。虽然我想我会在稍后的某个时间做一个更干净的代码。
    • 通过popen?如果执行 gnuplot 失败,你可以修改你传递给 popen 的命令行,给你一个很好的错误。
    • 你的意思是我应该读 errno?是的,我打算这样做。似乎是一个更好的主意。
    • 好吧,popen 不能通过errno 告诉你gnuplot 是否存在,因为popen 不会直接调用程序;它使用外壳。如果您在读取模式下使用popen,则仅在不读取任何数据的情况下获取 EOF可能表明运行 gnuplot 失败...
    • 但是内部的 fork() 或 pipe() 调用仍然设置了 errno,它们可以进行检查。在 POSIX 手册页中阅读。
    【解决方案4】:

    让哪个为您完成工作

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

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

    if (system("gnuplot --version"))
        /* not installed ... */
    

    【讨论】:

      【解决方案5】:

      我有类似的需求并通过复制 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" );
      
      
      }
      

      【讨论】:

        【解决方案6】:

        要在one of the previous answers 上构建,您可以使用getenv 获取PATH 的内容,然后迭代其组件。你可以使用strsep,而不是使用strchr

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            猜你喜欢
            • 2018-11-28
            • 2021-11-13
            • 1970-01-01
            • 1970-01-01
            • 2013-02-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-20
            相关资源
            最近更新 更多