Finding current executable's path without /proc/self/exe
在我看来,Linux使用/proc/self/exe很容易。但我想知道是否有一个方便的方法来找到当前应用程序的目录在C/C++与跨平台接口。我见过一些项目在argv[0]上乱作一团,但它似乎并不完全可靠。
如果你必须支持Mac OS X,它没有/proc/,你会怎么做?使用ifdefs隔离特定于平台的代码(例如nsbundle)?或者尝试从argv[0]、$path和其他什么来推断可执行文件的路径,这样会冒在边缘情况下发现错误的风险?
一些操作系统特定的接口:
- Mac OS X:
_NSGetExecutablePath() (男3岁) - Linux:
readlink /proc/self/exe - Solaris:
getexecname() 。 - Freebsd:
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1 。 - freebsd如果有procfs:
readlink /proc/curproc/file (freebsd默认没有procfs) - NetBSD:
readlink /proc/curproc/exe 。 - 蜻蜓BSD:
readlink /proc/curproc/file 。 - windows:
GetModuleFileName() ,带hModule =NULL 。
便携式(但不太可靠)方法是使用
一些shell,包括bash和ksh,在执行前将环境变量"EDOCX1"(14)设置为可执行文件的完整路径。在这种情况下,您可以使用
使用
这篇文章很长,但是讨论了实际的问题,并给出了代码,这些代码实际上与针对测试套件的验证一起工作。好的。
找到程序的最佳方法是追溯系统使用的相同步骤。这是通过使用针对文件系统根目录、pwd、路径环境和考虑符号链接以及路径名规范化的
要知道,几乎每个与Unix兼容的操作系统上发布的shell基本上都以相同的方式查找程序,并以几乎相同的方式设置操作环境(带有一些可选的额外功能)。而任何其他启动程序的程序都应该为该程序创建相同的环境(argv、环境字符串等),就像它是从shell运行的一样,带有一些可选的额外功能。程序或用户可以为其启动的其他下级程序设置一个偏离此约定的环境,但如果这样做,这是一个bug,并且程序没有合理的期望下级程序或其下级程序将正确运行。好的。
/path/to/executable —绝对路径../bin/executable —相对于PWDbin/executable —相对于PWD./foo —相对于PWDexecutable basename,在路径中查找bin//executable —相对于pwd,非规范src/../bin/executable —相对于pwd,非规范,回溯bin/./echoargc —相对于pwd,非规范
您不应该看到的值:好的。
~/bin/executable —在程序运行之前重写。~user/bin/executable —在程序运行之前重写alias —在程序运行之前重写$shellvariable —在程序运行之前重写*foo* 通配符,在程序运行前重写,不太有用?foo? 通配符,在程序运行前重写,不太有用
此外,这些可能包含非规范路径名和多层符号链接。在某些情况下,同一程序可能有多个硬链接。例如,
要找到自己,请遵循以下步骤:好的。
将pwd、path和argv[0]保存在程序条目(或库的初始化)上,因为它们稍后可能会更改。好的。
可选:特别是对于非UNIX系统,请分开,但不要丢弃路径名主机/用户/驱动器前缀部分(如果存在);通常位于冒号前面或后面的部分。好的。
如果
argv[0] 是绝对路径,则将其作为起点。绝对路径可能以"/"开头,但在某些非UNIX系统中,它可能以""开头,或者以驱动器号或名称前缀后跟冒号。好的。否则,如果
argv[0] 是一个相对路径(包含"/"或"",但没有以它开头,例如"../../bin/foo",则合并pwd+"/"+argv[0](在程序启动时使用当前工作目录,而不是当前目录)。好的。否则,如果argv[0]是一个普通的basename(没有斜线),则依次将其与path环境变量中的每个条目组合,并尝试这些项,并使用第一个成功的项。好的。
可选:否则,尝试特定于平台的
/proc/self/exe 、/proc/curproc/file (bsd)、(char *)getauxval(AT_EXECFN) 和dlgetname(...) (如果存在)。您甚至可以在基于argv[0] 的方法之前尝试这些方法,如果它们可用并且您没有遇到权限问题。在不太可能发生的情况下(当您考虑所有系统的所有版本时),它们都存在并且不会失败,它们可能更具权威性。好的。可选:检查使用命令行参数传入的路径名。好的。
可选:检查环境中由包装脚本显式传入的路径名(如果有)。好的。
可选:作为最后手段,请尝试使用环境变量"uu"。它可能完全指向不同的程序,例如用户shell。好的。
解析符号链接,可能有多个层。存在无限循环的可能性,但是如果它们存在,您的程序可能不会被调用。好的。
-
canonicalize文件名的城市。"resolving Foo类/ /……/酒吧/"/"两栏"。注意这可能存在潜在的变化,如果你在一个网络上的交叉点,立即册封的冰渣,并不总是一个很好的事。在一个网络服务器,"…"符号,可能被用来在两个导线A的路径上的另一个文件服务器的背景下,而不是在客户端。在这个案例中,你可能想要客户上下文SO规范化冰好。也convert样"模式/。/"两个"/"和"/"两个"/"。 壳体内,将多
readlink --canonicalize RESOLVE符号链接和canonicalize name。Chase可能做相似但不安装。如果realpath() 或canonicalize_file_name() ,指尖,可以帮助。好。
如果不存在
好。
请注意,多attempts可能是成功或部分成功的,他们可能不是所有的点的两个executable一样,所以你认为verifying executable;然而,你可能不想读的权限,如果你不能读它,我不知道,作为一个治疗失效。在邻近的一个或两个验证你的executable如"/……/ lib目录"你是想找一找。你可以有多个版本,由packaged和局部版和网络版,局域网和局域网和USB驱动的便携式版本,等。和有一个小的可能性,你可能会得到不同的结果从正常的双locating方法。和"_"可以简单的分两个错误的程序。
好。
一个程序可以使用
好。
您不一定需要为非UNIX系统编写代码,但最好了解一些特性,这样您就可以编写代码,这样以后就不会有人很难移植了。请注意,某些系统(dec-vms、dos、urls等)可能具有以冒号结尾的驱动器名或其他前缀,如"c:"、"sys$drive:[foo]bar"和"file:///foo/bar/baz"。旧的DEC VMS系统使用"["和"]"来包含路径的目录部分,但如果您的程序在POSIX环境中编译,则可能会发生更改。某些系统(如vms)可能具有文件版本(结尾用分号分隔)。有些系统使用两个连续斜杠,如"//drive/path/to/file"或"user@host:/path/to/file"(scp命令)或"file://hostname/path/to/file"(url)。在某些情况下(DOS、windoze),路径可能具有不同的分隔符字符-";"vs":"和"vs"/"作为路径分隔符。在csh/tsh中,有"path"(用空格分隔)和"path"(用冒号分隔),但是您的程序应该接收路径,所以您不必担心路径。DOS和其他一些系统可以有以驱动器前缀开头的相对路径。C:foo.exe是指驱动器C上当前目录中的foo.exe,因此您需要查找C:上的当前目录,并将其用于pwd。好的。
我的系统上的符号链接和包装器示例:好的。
1 2 3 4 5 | /usr/bin/google-chrome is symlink to /etc/alternatives/google-chrome which is symlink to /usr/bin/google-chrome-stable which is symlink to /opt/google/chrome/google-chrome which is a bash script which runs /opt/google/chome/chrome |
请注意,用户bill在上面发布了一个链接,指向HP的一个程序,该程序处理
- 使用
strncat() 和strncpy() ,需要重写所有strcat() 和strcpy() 。即使变量声明为长度pathmax,长度pathmax-1加上连接字符串的长度的输入值为>pathmax,长度pathmax的输入值将不会终止。 - 它需要重写为一个库函数,而不仅仅是打印出结果。
- 它无法规范化名称(使用上面链接的realpath代码)
- 它无法解析符号链接(使用realpath代码)
因此,如果您将HP代码和realpath代码结合在一起并修复它们以抵抗缓冲区溢出,那么您应该有能够正确解释
下面说明了在Ubuntu 12.04上调用同一程序的各种方法的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | cat ~/src/echoargc.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> main(int argc, char **argv) { printf(" argv[0]="%s" ", argv[0]); sleep(1); /* in case run from desktop */ } tcc -o ~/bin/echoargc ~/src/echoargc.c cd ~ /home/whitis/bin/echoargc argv[0]="/home/whitis/bin/echoargc" echoargc argv[0]="echoargc" bin/echoargc argv[0]="bin/echoargc" bin//echoargc argv[0]="bin//echoargc" bin/./echoargc argv[0]="bin/./echoargc" src/../bin/echoargc argv[0]="src/../bin/echoargc" cd ~/bin *echo* argv[0]="echoargc" e?hoargc argv[0]="echoargc" ./echoargc argv[0]="./echoargc" cd ~/src ../bin/echoargc argv[0]="../bin/echoargc" cd ~/junk ~/bin/echoargc argv[0]="/home/whitis/bin/echoargc" ~whitis/bin/echoargc argv[0]="/home/whitis/bin/echoargc" alias echoit=~/bin/echoargc echoit argv[0]="/home/whitis/bin/echoargc" echoarg=~/bin/echoargc $echoarg argv[0]="/home/whitis/bin/echoargc" ln -s ~/bin/echoargc junk1 ./junk1 argv[0]="./junk1" ln -s /home/whitis/bin/echoargc junk2 ./junk2 argv[0]="./junk2" ln -s junk1 junk3 ./junk3 argv[0]="./junk3" gnome-desktop-item-edit --create-new ~/Desktop # interactive, create desktop link, then click on it argv[0]="/home/whitis/bin/echoargc" # interactive, right click on gnome application menu, pick edit menus # add menu item for echoargc, then run it from gnome menu argv[0]="/home/whitis/bin/echoargc" cat ./testargcscript 2>&1 | sed -e 's/^/ /g' #!/bin/bash # echoargc is in ~/bin/echoargc # bin is in path shopt -s expand_aliases set -v cat ~/src/echoargc.c tcc -o ~/bin/echoargc ~/src/echoargc.c cd ~ /home/whitis/bin/echoargc echoargc bin/echoargc bin//echoargc bin/./echoargc src/../bin/echoargc cd ~/bin *echo* e?hoargc ./echoargc cd ~/src ../bin/echoargc cd ~/junk ~/bin/echoargc ~whitis/bin/echoargc alias echoit=~/bin/echoargc echoit echoarg=~/bin/echoargc $echoarg ln -s ~/bin/echoargc junk1 ./junk1 ln -s /home/whitis/bin/echoargc junk2 ./junk2 ln -s junk1 junk3 ./junk3 |
这些例子说明,本篇文章中描述的技术应该在各种各样的情况下工作,以及为什么需要某些步骤。好的。
编辑:现在,打印argv[0]的程序已经被更新,以实际找到自己。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | // Copyright 2015 by Mark Whitis. License=MIT style #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <limits.h> #include #include <string.h> #include <errno.h> //"look deep into yourself, Clarice" -- Hanibal Lector char findyourself_save_pwd[PATH_MAX]; char findyourself_save_argv0[PATH_MAX]; char findyourself_save_path[PATH_MAX]; char findyourself_path_separator='/'; char findyourself_path_separator_as_string[2]="/"; char findyourself_path_list_separator[8]=":"; // could be":;" char findyourself_debug=0; int findyourself_initialized=0; void findyourself_init(char *argv0) { getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd)); strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0)); findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0; strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path)); findyourself_save_path[sizeof(findyourself_save_path)-1]=0; findyourself_initialized=1; } int find_yourself(char *result, size_t size_of_result) { char newpath[PATH_MAX+256]; char newpath2[PATH_MAX+256]; assert(findyourself_initialized); result[0]=0; if(findyourself_save_argv0[0]==findyourself_path_separator) { if(findyourself_debug) printf(" absolute path "); realpath(findyourself_save_argv0, newpath); if(findyourself_debug) printf(" newpath="%s" ", newpath); if(!access(newpath, F_OK)) { strncpy(result, newpath, size_of_result); result[size_of_result-1]=0; return(0); } else { perror("access failed 1"); } } else if( strchr(findyourself_save_argv0, findyourself_path_separator )) { if(findyourself_debug) printf(" relative path to pwd "); strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; strncat(newpath2, findyourself_save_argv0, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; realpath(newpath2, newpath); if(findyourself_debug) printf(" newpath="%s" ", newpath); if(!access(newpath, F_OK)) { strncpy(result, newpath, size_of_result); result[size_of_result-1]=0; return(0); } else { perror("access failed 2"); } } else { if(findyourself_debug) printf(" searching $PATH "); char *saveptr; char *pathitem; for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator, &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) { if(findyourself_debug>=2) printf("pathitem="%s" ", pathitem); strncpy(newpath2, pathitem, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; strncat(newpath2, findyourself_save_argv0, sizeof(newpath2)); newpath2[sizeof(newpath2)-1]=0; realpath(newpath2, newpath); if(findyourself_debug) printf(" newpath="%s" ", newpath); if(!access(newpath, F_OK)) { strncpy(result, newpath, size_of_result); result[size_of_result-1]=0; return(0); } } // end for perror("access failed 3"); } // end else // if we get here, we have tried all three methods on argv[0] and still haven't succeeded. Include fallback methods here. return(1); } main(int argc, char **argv) { findyourself_init(argv[0]); char newpath[PATH_MAX]; printf(" argv[0]="%s" ", argv[0]); realpath(argv[0], newpath); if(strcmp(argv[0],newpath)) { printf(" realpath="%s" ", newpath); } find_yourself(newpath, sizeof(newpath)); if(1 || strcmp(argv[0],newpath)) { printf(" findyourself="%s" ", newpath); } sleep(1); /* in case run from desktop */ } |
这里是输出,它证明在之前的每一个测试中,它确实找到了自己。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | tcc -o ~/bin/echoargc ~/src/echoargc.c cd ~ /home/whitis/bin/echoargc argv[0]="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" echoargc argv[0]="echoargc" realpath="/home/whitis/echoargc" findyourself="/home/whitis/bin/echoargc" bin/echoargc argv[0]="bin/echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" bin//echoargc argv[0]="bin//echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" bin/./echoargc argv[0]="bin/./echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" src/../bin/echoargc argv[0]="src/../bin/echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" cd ~/bin *echo* argv[0]="echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" e?hoargc argv[0]="echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" ./echoargc argv[0]="./echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" cd ~/src ../bin/echoargc argv[0]="../bin/echoargc" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" cd ~/junk ~/bin/echoargc argv[0]="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" ~whitis/bin/echoargc argv[0]="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" alias echoit=~/bin/echoargc echoit argv[0]="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" echoarg=~/bin/echoargc $echoarg argv[0]="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" rm junk1 junk2 junk3 ln -s ~/bin/echoargc junk1 ./junk1 argv[0]="./junk1" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" ln -s /home/whitis/bin/echoargc junk2 ./junk2 argv[0]="./junk2" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" ln -s junk1 junk3 ./junk3 argv[0]="./junk3" realpath="/home/whitis/bin/echoargc" findyourself="/home/whitis/bin/echoargc" |
上面描述的两个GUI启动也可以正确地找到程序。好的。
有一个潜在的陷阱。如果测试前程序是setuid,那么
从GregoryPakosz(它只有一个C文件)中查看Wherami库;它允许您获取到各种平台上当前可执行文件的完整路径。目前,它在GitHub上作为回购提供。
Linux上使用
1 2 3 4 5 6 7 8 9 | #include <stdio.h> #include <sys/auxv.h> int main(int argc, char **argv) { printf("%s ", (char *)getauxval(AT_EXECFN)); return(0); } |
注意,
跨平台可靠地工作需要使用ifdef语句。
下面的代码在Windows、Linux、MacOS、Solaris或FreeBSD中查找可执行文件的路径(尽管FreeBSD未经测试)。它使用boost>=1.55.0来简化代码,但如果需要,它很容易删除。正如操作系统和编译器所要求的那样,只需使用像msc-ver和linux这样的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #include <string> #include <boost/predef/os.h> #if (BOOST_OS_WINDOWS) # include <stdlib.h> #elif (BOOST_OS_SOLARIS) # include <stdlib.h> # include <limits.h> #elif (BOOST_OS_LINUX) # include <unistd.h> # include <limits.h> #elif (BOOST_OS_MACOS) # include <mach-o/dyld.h> #elif (BOOST_OS_BSD_FREE) # include <sys/types.h> # include <sys/sysctl.h> #endif /* * Returns the full path to the currently running executable, * or an empty string in case of failure. */ std::string getExecutablePath() { #if (BOOST_OS_WINDOWS) char *exePath; if (_get_pgmptr(&exePath) != 0) exePath =""; #elif (BOOST_OS_SOLARIS) char exePath[PATH_MAX]; if (realpath(getexecname(), exePath) == NULL) exePath[0] = '\0'; #elif (BOOST_OS_LINUX) char exePath[PATH_MAX]; ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath)); if (len == -1 || len == sizeof(exePath)) len = 0; exePath[len] = '\0'; #elif (BOOST_OS_MACOS) char exePath[PATH_MAX]; uint32_t len = sizeof(exePath); if (_NSGetExecutablePath(exePath, &len) != 0) { exePath[0] = '\0'; // buffer too small (!) } else { // resolve symlinks, ., .. if possible char *canonicalPath = realpath(exePath, NULL); if (canonicalPath != NULL) { strncpy(exePath,canonicalPath,len); free(canonicalPath); } } #elif (BOOST_OS_BSD_FREE) char exePath[2048]; int mib[4]; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; size_t len = sizeof(exePath); if (sysctl(mib, 4, exePath, &len, NULL, 0) != 0) exePath[0] = '\0'; #endif return std::string(exePath); } |
上面的版本返回包括可执行文件名在内的完整路径。如果您希望路径不带可执行文件名,
1 | return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string(); |
If you ever had to support, say, Mac
OS X, which doesn't have /proc/, what
would you have done? Use #ifdefs to
isolate the platform-specific code
(NSBundle, for example)?
是的,使用
另一种方法是使用一个包含函数声明并将实现放在特定于平台的源文件中的无需清理
您可以使用argv[0]并分析path环境变量。看看:一个程序的例子
根据QNX中微子的版本,有不同的方法来查找用于启动运行进程的可执行文件的完整路径和名称。我将进程标识符表示为
我希望这有帮助。
获取可执行映像路径名的更可移植方法:
如果您具有进程ID,ps可以为您提供可执行文件的路径。另外,ps是一个posix实用程序,因此它应该是可移植的
因此,如果进程ID是249297,那么这个命令只提供路径名。
1 | ps -p 24297 -o comm --no-heading |
论据的解释
-P-选择给定的过程
-o comm-显示命令名(-o cmd选择整个命令行)
--无标题-不显示标题行,只显示输出。
C程序可以通过popen运行它。
阿法克,不是这样的。还有一个问题:如果同一个可执行文件有多个"指向"它的硬链接,您希望得到什么样的答案?(硬链接实际上不是"指向",它们是同一个文件,只是在fs层次结构中的另一个位置。)一旦execve()成功地执行了一个新的二进制文件,它的参数的所有信息都将丢失。
如果使用c,则可以使用getwd函数:
1 2 3 4 5 6 | int main() { char buf[4096]; getwd(buf); printf(buf); } |
这将在标准输出上打印可执行文件的当前目录。
一个程序的绝对值路径在主函数envp的pwd中,还有一个函数在c中称为getenv,所以就是这样。