如何获取程序运行的目录?

291
有没有一种与平台无关和文件系统无关的方法,可以使用C/C++获取程序运行的完整目录路径?不要与当前工作目录混淆。(请不要建议除了标准库(如clib或STL)之外的其他库。)
(如果没有平台/文件系统无关的方法,也欢迎在Windows和Linux上针对特定文件系统的建议。)

4
除非您可以可靠地从argv [0]中提取路径,否则该技术将非常依赖于操作系统。 - David R Tribble
2
仅澄清一下:所谓的“当前目录”或者说“程序运行的目录”(在问题的术语中),是指程序图像文件(~.exe 文件)所在的目录,而“当前工作目录”则是指当程序使用相对路径时自动补全的目录? - colemik
4
当你 #include <windows.h> 时,Windows会自动将可执行文件路径的 char* 存储在 _pgmptr 中。如果你只在Windows上工作,就不需要调用额外的函数或假设无用信息。 - rsethc
这回答了你最初的问题吗?https://github.com/gpakosz/whereami - Gregory Pakosz
3
虽然这条评论是三年前的,但我想进一步解释rsethc有关_pgmptr 的评论。MSDN文档说明 _pgmptr_wpgmptr 变量已被弃用,应该使用函数 _get_pgmptr(char**)_get_wpgmptr(wchar_t**) 代替。MSDN - Hydranix
显示剩余2条评论
26个回答

200
以下是获取执行应用程序的完整路径的代码:
变量声明:
char pBuf[256];
size_t len = sizeof(pBuf); 

Windows:

int bytes = GetModuleFileName(NULL, pBuf, len);
return bytes ? bytes : -1;

Linux:

int bytes = MIN(readlink("/proc/self/exe", pBuf, len), len - 1);
if(bytes >= 0)
    pBuf[bytes] = '\0';
return bytes;

7
由于某些原因,在OS X上不支持/proc/pid/exe。 - Chris Lutz
30
当我看到那些查看/proc的代码时,我的心情会稍稍凋谢。全世界并不只有Linux这一个平台,即使在这个平台上,/proc也应该被视为易受版本、架构等影响而改变的东西。 - asveikau
3
顺便说一下,获取 Unix 上二进制文件路径的更便携方式是查看 argv[0],但这可能会被启动你的进程所伪造。 - asveikau
4
如果在Linux上使用别名命令启动程序,那么argv[0]是“命令的名称”还是展开后的名称? - Andy Dent
21
可以添加 char pBuf[256]; size_t len = sizeof(pBuf); 以使解决方案更加清晰。 - charles.cc.hsu
显示剩余7条评论

172

如果在程序启动时获取当前目录,则可以有效地获得程序启动的目录。将该值存储在变量中,并在程序的后续部分中引用它。这与包含当前可执行程序文件的目录不同。它并不一定是相同的目录;如果有人从命令提示符运行该程序,则程序将从命令提示符的当前工作目录运行,即使程序文件位于其他位置也是如此。

getcwd是一个POSIX函数,在所有符合POSIX标准的平台上都支持。您不需要做任何特殊处理(除了在Unix上包含正确的头文件unistd.h,在Windows上包含direct.h)。

由于您正在创建一个C程序,它将连接到默认的C运行时库,该库由系统中的所有进程链接(避免特别制作的异常),并且默认情况下它将包括此函数。CRT从未被视为外部库,因为它提供基本的标准兼容接口到操作系统。

在Windows上,getcwd函数已被弃用,取而代之的是_getcwd。我认为您可以以这种方式使用它。

#include <stdio.h>  /* defines FILENAME_MAX */
#ifdef WINDOWS
    #include <direct.h>
    #define GetCurrentDir _getcwd
#else
    #include <unistd.h>
    #define GetCurrentDir getcwd
 #endif

 char cCurrentPath[FILENAME_MAX];

 if (!GetCurrentDir(cCurrentPath, sizeof(cCurrentPath)))
     {
     return errno;
     }

cCurrentPath[sizeof(cCurrentPath) - 1] = '\0'; /* not really required */

printf ("The current working directory is %s", cCurrentPath);

49
好的回答,但我认为“当前工作目录”不是所需内容。 - Michael Burr
4
请添加一句话,即使某些文档说cCurrentpath可以为null并且将由getcwd分配,但在Mac OS上,getcwd似乎不会分配任何东西,并会悄悄地导致程序崩溃。 - Janusz
4
有一个小错误,但是很遗憾我现在还不能编辑... 第10行:cCurrentpath应该改为cCurrentPath - Lipis
8
在Windows上,应该尽量避免使用以POSIX命名的函数(其中一些以下划线开头)。这些函数并不是真正的Windows API,而只是CRT。你想要使用的是Windows API中的GetCurrentDirectory()。http://msdn.microsoft.com/en-us/library/aa364934(VS.85).aspx - asveikau
6
Mike的回答是正确的。 "当前目录"并不总是与二进制文件运行的目录相同。例如,如果一个应用程序在Windows上作为服务运行,则当前目录可能是C:\Windows\System32,而二进制目录不同。 - Lucky Luke
显示剩余8条评论

47

这是来自于C++论坛

在 Windows 上:

#include <string>
#include <windows.h>

std::string getexepath()
{
  char result[ MAX_PATH ];
  return std::string( result, GetModuleFileName( NULL, result, MAX_PATH ) );
}

在Linux上:

#include <string>
#include <limits.h>
#include <unistd.h>

std::string getexepath()
{
  char result[ PATH_MAX ];
  ssize_t count = readlink( "/proc/self/exe", result, PATH_MAX );
  return std::string( result, (count > 0) ? count : 0 );
}

在 HP-UX 上:

#include <string>
#include <limits.h>
#define _PSTAT64
#include <sys/pstat.h>
#include <sys/types.h>
#include <unistd.h>

std::string getexepath()
{
  char result[ PATH_MAX ];
  struct pst_status ps;

  if (pstat_getproc( &ps, sizeof( ps ), 0, getpid() ) < 0)
    return std::string();

  if (pstat_getpathname( result, PATH_MAX, &ps.pst_fid_text ) < 0)
    return std::string();

  return std::string( result );
}

1
那个Windows解决方案无法处理路径中的非ANSI字符。你可能应该使用GetModuleFileNameW并显式地将其转换为UTF-8(在需要发出文件系统命令时小心地将其转换回来)。 - Adrian McCarthy
4
在使用MinGW编译时,对于Windows解决方案,我遇到了错误“error: cannot convert 'char*' to 'LPWCH {aka wchar_t*}' for argument '2' to 'DWORD GetModuleFileNameW(HMODULE, LPWCH, DWORD)'”。 - HelloGoodbye
2
@Adrian,我通常不是Windows程序员,但是难道没有一种定义或者某种方式可以让你的编译器自动使用_W()函数吗? - Octopus
1
@Octopus:要使用宽字符调用,您需要使用WCHAR(而不是char)和std::wstring(而不是std::string)。 - Adrian McCarthy

30
如果你想使用没有库的标准方法:不行。整个目录概念未包含在标准中。
如果你同意使用一些(可移植)依赖于接近标准的库:使用Boost's filesystem library并请求initial_path()
在我看来,这是你可以得到的最接近的方式,拥有良好的信誉(Boost是一个成熟的高质量库集合)。

8
Boost文档中的内容: template <class Path> const Path& initial_path();返回值: main()函数入口时的current_path()。而current_path()的行为类似于POSIX getcwd()。这并不是提问者所要求的内容。 - Jonathan Leffler
请查看boost 1.46.1的http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/reference.html#initial_path。 - moala
1
根据注释,这会提供二进制文件被调用的路径,而不是二进制文件的路径...因为它可能是从不同的文件夹启动的。 - jpo38

24

我知道这个回答很晚了,但是我发现没有一个回答对我有用,所以我找到了自己的解决方案。获取从当前工作目录到bin文件夹的路径的一个非常简单的方法如下:

int main(int argc, char* argv[])
{
    std::string argv_str(argv[0]);
    std::string base = argv_str.substr(0, argv_str.find_last_of("/"));
}

现在你可以将这个作为相对路径的基础。例如,我有以下目录结构:

main
  ----> test
  ----> src
  ----> bin

我想将我的源代码编译成二进制文件,并写入一个日志以测试。我只需要在我的代码中添加这一行。

std::string pathToWrite = base + "/../test/test.log";

我已经在Linux上使用了完整路径、别名等尝试了这种方法,效果非常好。

注意:

如果你在Windows上,应该使用 '\' 作为文件分隔符而不是 '/'. 你也需要将它转义,例如:

std::string base = argv[0].substr(0, argv[0].find_last_of("\\"));

我认为这应该可行,但尚未测试,因此如果它起作用,请留下评论;如果不起作用,请提供修复建议。


是的,它也可以在Windows上运行。我认为这是最好的解决方案。据我所知,argv [0]始终保留可执行文件的路径。 - Wodzu
4
argv[0] 是一个非常好的想法,但不幸的是,在 Linux 上我得到的是 "./my_executable_name" 或 "./make/my_executable_name"。基本上,我得到的取决于我如何启动它。 - Xeverous
1
@Xeverous:那又怎样?如果我有一些与我的可执行文件相关的文件需要打开,从“./”或“./make/”开始在你们的情况下应该可以工作。“.”是当前工作目录,而argv[0]将告诉您相对路径到可执行文件,这正是OP所需的。无论如何,这正是我所需要的。 - nilo

20

文件系统TS 现在是一个标准(并且由gcc 5.3+和clang 3.9+支持),因此您可以使用它的current_path()函数:

std::string path = std::experimental::filesystem::current_path();

在gcc (5.3+)中,要包含Filesystem,您需要使用:

#include <experimental/filesystem>

并使用-lstdc++fs标志将您的代码链接。

如果您想在Microsoft Visual Studio中使用文件系统,请阅读此内容


13
从参考链接中可以得知,1-2)返回当前工作目录的绝对路径,类似于 POSIX getcwd 的功能。(2)如果出现错误,则返回 path()。该评论被投票降低了赞成数,因为原帖作者特别询问了可执行文件的当前路径而非当前工作目录。 - S. Saad

10

在Windows上,最简单的方法是使用 stdlib.h 中的_get_pgmptr函数获取指向表示可执行文件的绝对路径(包括可执行文件的名称)的字符串的指针。

char* path;
_get_pgmptr(&path);
printf(path); // Example output: C:/Projects/Hello/World.exe

如果那是编译时常量,如果有人移动您的程序会怎么样? - aremmell

10

没有标准的方法。我相信C/C++标准甚至不考虑目录(或其他文件系统组织)的存在。

在Windows上,当hModule参数设置为NULL时,GetModuleFileName()将返回当前进程可执行文件的完整路径。我无法提供Linux方面的帮助。

另外,您应该澄清您是想要当前目录还是程序映像/可执行文件所在的目录。就目前而言,您的问题在这一点上有点模棱两可。


8
也许可以将当前工作目录与argv [0]连接起来?我不确定在Windows中是否有效,但在Linux中有效。
例如:
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv) {
    char the_path[256];

    getcwd(the_path, 255);
    strcat(the_path, "/");
    strcat(the_path, argv[0]);

    printf("%s\n", the_path);

    return 0;
}

运行时,它会输出:

jeremy@jeremy-desktop:~/Desktop$ ./test
/home/jeremy/Desktop/./test


你需要检查一下argv[0]中是否给出了绝对路径。但更重要的是,如果图像是通过PATH定位的呢?Linux会填充完整路径还是只填充命令行上的内容? - Michael Burr
正如Mike B所指出的那样,这是一种非通用解决方案;它只在一些非常有限的情况下起作用。基本上,只有当您通过相对路径名运行命令时才有效 - 当您运行../../../bin/progname而不是./test时,它并不那么优雅。 - Jonathan Leffler
如果解析相对于当前目录的argv[0]可能的相对路径(因为argv[0]可以是“../../myprogram.exe”),那可能是回答这个问题最安全的方法。它始终有效,并且是可移植的(它甚至可以在Android上工作!)。 - jpo38

7

这个函数存在一个很大的问题:多线程应用程序和共享库代码不应该使用GetCurrentDirectory函数,也应该避免使用相对路径名。如果您能够接受这个假设,那么它是最好的解决方案。 - McLeary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接