我认为Linux的/proc/self/exe很容易使用。但我想知道是否有跨平台接口的方便方法来查找当前应用程序的目录。我见过一些项目在argv[0]上操作,但它似乎不是完全可靠的。
如果您曾经需要支持例如Mac OS X这样没有/proc/的系统,您会怎么做?使用#ifdef来隔离特定于平台的代码(例如NSBundle)吗?还是尝试从argv [0],$PATH等中推断出可执行文件的路径,冒着在边缘情况下发现错误的风险?
我认为Linux的/proc/self/exe很容易使用。但我想知道是否有跨平台接口的方便方法来查找当前应用程序的目录。我见过一些项目在argv[0]上操作,但它似乎不是完全可靠的。
如果您曾经需要支持例如Mac OS X这样没有/proc/的系统,您会怎么做?使用#ifdef来隔离特定于平台的代码(例如NSBundle)吗?还是尝试从argv [0],$PATH等中推断出可执行文件的路径,冒着在边缘情况下发现错误的风险?
_NSGetExecutablePath()
(man 3 dyld)readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD默认情况下没有procfs)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
,其中hModule
= NULL
还有第三方库可以用来获取这些信息,例如whereami(在Prideout的答案中提到),或者如果您正在使用Qt,则可以使用QCoreApplication :: applicationFilePath(),如评论中所述。
便携式(但不太可靠)的方法是使用 argv [0] 。尽管调用程序可以将其设置为任何内容,但通常将其设置为可执行文件的路径名或使用
$ PATH 找到的名称。
一些Shell(包括bash和ksh)设置环境变量“ _ ”
为执行之前的完整路径。在这种情况下,您可以使用 getenv("_") 来获取它。但是,这是不可靠的,因为并非所有Shell都这样做,它可能被设置为任何内容,或者从未更改过父进程留下的内容,在执行程序之前没有更改它。
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
;这与getexecname()
不同 - 后者执行相当于pargs -x <PID> | grep AT_SUN_EXECNAME
的操作... - FrankH./proc/self/exe
是不可移植和不可靠的。在我的Ubuntu 12.04系统上,您必须以root身份才能读取/遵循符号链接。这将使Boost示例和可能发布的whereami()
解决方案失败。找到你的程序的最好方法是重复系统使用的相同步骤。这可以通过使用argv[0]
解析根文件系统、pwd、路径环境以及考虑符号链接和路径名规范化来完成。我记得以前我曾经成功地做过这个,并在各种不同的情况下进行了测试。它不能保证一定有效,但如果它无效,那么你可能有更大的问题,而且总体上比其他讨论过的任何方法更可靠。在Unix兼容系统中,有些情况下正确处理argv[0]
可能无法帮助你找到你的程序,但那时你正在执行一个明显有问题的环境。它也相当适用于所有Unix衍生系统,自1970年以来甚至包括一些非Unix衍生系统,因为它基本上依赖于libc()标准功能和标准命令行功能。它应该在Linux(所有版本)、Android、Chrome OS、Minix、原始贝尔实验室Unix、FreeBSD、NetBSD、OpenBSD、BSD x.x、SunOS、Solaris、SYSV、HP-UX、Concentrix、SCO、Darwin、AIX、OS X、NeXTSTEP等系统上工作。并且通过一些修改可能还适用于VMS、VM/CMS、DOS/Windows、ReactOS、OS/2等系统。如果一个程序是直接从GUI环境启动的,它应该将argv[0]
设置为绝对路径。
argv[0]
可能的值包括:
/path/to/executable
— 绝对路径../bin/executable
— 相对于pwdbin/executable
— 相对于pwd./foo
— 相对于pwdexecutable
— 基本名称,在路径中查找bin//executable
— 相对于pwd,非规范的src/../bin/executable
— 相对于pwd,非规范的,回溯bin/./echoargc
— 相对于pwd,非规范的您不应该看到的值:
~/bin/executable
— 在程序运行之前被重写。~user/bin/executable
— 在程序运行之前被重写alias
— 在程序运行之前被重写$shellvariable
— 在程序运行之前被重写*foo*
— 通配符,在程序运行之前被重写,不是很有用?foo?
— 通配符,在程序运行之前被重写,不是很有用/bin/ls
,/bin/ps
,/bin/chmod
,/bin/rm
等可能是指向/bin/busybox
的硬链接。在程序入口处(或库的初始化)保存pwd、PATH和argv[0],因为它们可能会在后面改变。
可选:特别是对于非Unix系统,将路径名主机/用户/驱动器前缀部分分离出来但不要丢弃,如果存在的话;这部分通常在冒号之前或在初始“//”之后。
如果 argv [0]
是绝对路径,则使用该路径作为起点。 绝对路径可能以“ / ”开头,但在某些非Unix系统上,它可能以“”或驱动器字母或名称前缀开头,后跟冒号。
否则,如果 argv [0]
是相对路径(包含“ / ”或“”但不以此开头,例如“ ../../bin/foo”),则将pwd +“ / ”+ argv [0]组合起来(使用程序启动时的当前工作目录,而不是当前目录)。
否则,如果argv [0]是一个纯粹的基本名称(没有斜杠),则将其与PATH环境变量中的每个条目结合起来,依次尝试并使用第一个成功的。
可选:否则尝试非常特定于平台的/proc/self/exe
,/proc/curproc/file
(BSD),(char *)getauxval(AT_EXECFN)
和dlgetname(...)
(如果存在)。 如果它们可用并且您没有遇到权限问题,则甚至可以在基于argv [0]的方法之前尝试这些方法。 在考虑所有版本的所有系统时,如果它们存在且不失败,则可能更有权威性。
可选:检查是否使用命令行参数传递了路径名。
可选:检查是否由包装脚本显式传递了环境中的路径名。
可选:最后尝试环境变量“_”。 它可能指向完全不同的程序,例如用户的shell。
解析符号链接,可能有多个层。 存在无限循环的可能性,但如果存在,您的程序可能无法调用。
通过解析类似“ / foo / .. / bar /”之类的子字符串来使文件名规范化为“ / bar /”。 请注意,如果跨越网络挂载点,则此操作可能会改变含义,因此规范化并不总是好事。 在网络服务器上,“..”在符号链接中可能用于遍历到服务器上的另一个文件而不是客户端。 在这种情况下,您可能需要客户端上下文,因此规范化是可以的。 还将类似“ / ./”和“ //”的模式转换为“ / ”。
在shell中,readlink --canonicalize
将解析多个符号链接并使名称规范化。 Chase可能会执行类似的操作,但未安装。 如果存在,则realpath()
或canonicalize_file_name()
可能有所帮助。
realpath()
,您可以从一个许可宽松的库分发中借用副本,并将其自己编译,而不是重复造轮子。如果您将使用小于 PATH_MAX 的缓冲区,请修复潜在的缓冲区溢出(传递 sizeof 输出缓冲区,考虑 strncpy() vs strcpy())。使用已重命名的私有副本可能更容易,而不是测试其是否存在。从 android/darwin/bsd 中获取许可宽松的副本:
https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
请注意,多次尝试可能会成功或部分成功,并且它们可能不都指向相同的可执行文件,因此请考虑验证您的可执行文件;但是,如果您无法读取它,则可能没有读取权限,请不要将其视为失败。或者验证与您的可执行文件相邻的某些内容,例如您正在查找的“../lib/”目录。您可能有多个版本,打包和本地编译版本,本地和网络版本以及本地和USB驱动器便携版本等,而且有很小的可能性您可能从不同的定位方法中获得两个不兼容的结果。 "_" 可能只是指向错误的程序。我的系统上符号链接和包装器的一个例子:
/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
argv[0]
三种基本情况的程序的链接。不过它还需要一些更改:
strcat()
和strcpy()
,使用strncat()
和strncpy()
。即使变量声明为PATHMAX长度,长度为PATHMAX-1加上连接字符串的长度的输入值大于PATHMAX,长度为PATHMAX的输入值也将无法终止。因此,如果您将HP代码和realpath代码组合起来,并修复它们以抵抗缓冲区溢出,则应该可以正确解释argv[0]
。
argv[0]
的实际值。是的,该程序被意外地命名为echoargc而不是echoargv。这是使用脚本进行干净复制完成的,但在shell中手动执行会得到相同的结果(除非您明确启用别名)。cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
printf(" argv[0]=\"%s\"\n", 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
// Copyright 2015 by Mark Whitis. License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#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\n");
realpath(findyourself_save_argv0, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", 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\n");
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\"\n", 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\n");
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\"\n", 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\"\n", 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\"\n", argv[0]);
realpath(argv[0], newpath);
if(strcmp(argv[0],newpath)) { printf(" realpath=\"%s\"\n", newpath); }
find_yourself(newpath, sizeof(newpath));
if(1 || strcmp(argv[0],newpath)) { printf(" findyourself=\"%s\"\n", newpath); }
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"
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权限,则access()
函数会放弃权限。如果存在一种情况,即程序可以作为提升的用户而不是普通用户找到,那么这些测试可能会失败,尽管在这种情况下程序实际上可能无法执行。可以使用euidaccess()代替。但是,它可能会比实际用户更早地在路径上找到一个无法访问的程序。
strncpy()
也不安全使用strncat()
,尤其是strncat()
。strncpy()
不能保证空终止;如果源字符串比目标空间长,则该字符串没有空终止符。strncat()
非常难以使用;即使target
最初是一个空字符串,strncat(target, source, sizeof(target))
也是错误的,如果source
比目标长。长度是可以安全附加到目标的字符数,不包括尾随的空值,因此sizeof(target)-1
是最大值。 - Jonathan Lefflerwhereami库由Gregory Pakosz实现,使用mark4o的文章中提到的API来支持各种平台。如果您“只是”需要适用于可移植项目的解决方案,并且不关心不同平台的特殊性,则这最为有趣。
截至撰写本文时,支持的平台有:
该库由whereami.c
和whereami.h
组成,根据MIT和WTFPL2许可证进行许可。将这些文件放入您的项目中,包含头文件并使用它:
#include "whereami.h"
int main() {
int length = wai_getExecutablePath(NULL, 0, NULL);
char* path = (char*)malloc(length + 1);
wai_getExecutablePath(path, length, &dirname_length);
path[length] = '\0';
printf("My path: %s", path);
free(path);
return 0;
}
在Linux上,使用 /proc/self/exe
或 argv[0]
之外的另一种选择是使用由ELF解释器传递的信息,glibc将其作为可用信息提供:
#include <stdio.h>
#include <sys/auxv.h>
int main(int argc, char **argv)
{
printf("%s\n", (char *)getauxval(AT_EXECFN));
return(0);
}
需要注意的是,getauxval
是 glibc 的扩展功能,为了具有健壮性,您应该检查它是否返回 NULL
(表示 ELF 解释器未提供 AT_EXECFN
参数),但我认为这在 Linux 上实际上并不是一个问题。
./build/foo
启动我的进程,你的解决方案会返回 ./build/foo
,这是毫无用处的。(Debian Bullseye) - BitTickler.
是什么,我实际上无法将路径绝对化...最终我使用了readlink解决方案,它返回一个绝对路径。 - BitTickler。
是什么,只需调用getcwd()
即可。即使你不知道,而且你只需要进入上一级目录,也总有..
。 - Dolda2000要可靠地在各个平台上运行,需要使用 #ifdef 语句。
以下代码可以在 Windows、Linux、MacOS、Solaris 或 FreeBSD(尽管未经测试)中找到可执行文件的路径。它使用 Boost 1.55.0(或更高版本)来简化代码,但如果您想要移除它也很容易。只需根据操作系统和编译器的要求使用定义如 _MSC_VER 和 __linux。
#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);
}
#include boost/filesystem.hpp>
并将返回语句更改为:return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
execve()
成功执行新的二进制文件,关于原始程序的参数的所有信息都会丢失。relocatable-prog
模块是一种不错的选择。libintl
相关的任何操作之前):set_program_name (argv[0]);
此函数的原型在 progname.h
中。”这意味着需要程序员干预。不清楚这是否是预期的。 - Jonathan Lefflerset_program_name
,是的。不确定你所说的“不清楚这是否是预期的意图”的意思。 - Reuben Thomas
ps -o comm
。 让我来到这里的是:"/proc/pid/path/a.out"。 - basinstd::filesystem::current_path()
不会返回当前可执行文件的路径,而是返回当前工作目录的路径。 - Ted Lyngmo