Нахождение пути текущего исполняемого файла без /proc/self/exe

Мне кажется, что в Linux все проще с /proc/self/exe. Но я хотел бы знать, есть ли удобный способ найти каталог текущего приложения в C/C++ с кросс-платформенными интерфейсами. Я видел, как некоторые проекты возились с argv[0], но это не совсем надежно.

Если бы вам когда-нибудь приходилось поддерживать, скажем, Mac OS X, у которой нет / proc /, что бы вы сделали? Используйте #ifdefs для изоляции кода, специфичного для платформы (например, NSBundle)? Или попробуйте вывести путь к исполняемому файлу из argv[0], $PATH и прочего, рискуя найти ошибки в крайних случаях?

18 ответов

Некоторые специфичные для ОС интерфейсы:

  • 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
  • DragonFly BSD: readlink /proc/curproc/file
  • Окна: GetModuleFileName() с hModule знак равно NULL

Портативный (но менее надежный) метод заключается в использовании argv[0], Хотя вызывающая программа может установить для нее что угодно, по соглашению она может быть либо путевым именем исполняемого файла, либо именем, которое было найдено с помощью $PATH,

Некоторые оболочки, включая bash и ksh, устанавливают переменную окружения _ " к полному пути исполняемого файла перед его выполнением. В этом случае вы можете использовать getenv("_") чтобы получить это. Однако это ненадежно, потому что не все оболочки делают это, и его можно установить на что-либо или оставить в родительском процессе, который не изменил его перед выполнением вашей программы.

Использование /proc/self/exe не переносимый и ненадежный. В моей системе Ubuntu 12.04 вы должны быть пользователем root, чтобы читать / следовать символической ссылке. Это сделает пример Boost и, вероятно, whereami() опубликованные решения провалились.

Этот пост очень длинный, но в нем обсуждаются актуальные проблемы и представлен код, который фактически работает вместе с проверкой на соответствие тестовому набору.

Лучший способ найти вашу программу - повторить те же шаги, которые использует система. Это делается с помощью argv[0] разрешен для корня файловой системы, pwd, среды пути и с учетом символических ссылок и канонизации пути. Это по памяти, но я успешно делал это в прошлом и проверял в различных ситуациях. Он не гарантированно работает, но если этого не произойдет, у вас, вероятно, возникнут гораздо большие проблемы, и в целом он более надежен, чем любой из других обсуждаемых методов. В Unix-совместимой системе существуют ситуации, в которых правильная обработка argv[0] не получит вас к вашей программе, но тогда вы выполняете в явно нарушенной среде. Он также довольно переносим для всех производных систем Unix начиная с 1970 года и даже для некоторых не производных от Unix систем, поскольку он в основном опирается на стандартные функции libc() и стандартные функции командной строки. Он должен работать на Linux (все версии), Android, Chrome OS, Minix, оригинальной версии Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Nextstep и т. Д. И с небольшой модификацией, вероятно, VMS, VM/CMS, DOS/Windows, ReactOS, OS/2 и т. Д. Если программа была запущена непосредственно из среды графического интерфейса, она должна была установить argv[0] на абсолютный путь.

Поймите, что почти каждая оболочка в каждой Unix-совместимой операционной системе, которая когда-либо выпускалась, в основном находит программы одинаково и настраивает операционную среду практически одинаково (с некоторыми дополнительными дополнениями). Ожидается, что любая другая программа, которая запускает программу, создаст ту же среду (argv, строки окружения и т. Д.) Для этой программы, как если бы она была запущена из оболочки, с некоторыми дополнительными дополнениями. Программа или пользователь могут настроить среду, которая отличается от этого соглашения для других подчиненных программ, которые он запускает, но если это так, то это ошибка, и у программы нет разумных ожиданий, что подчиненная программа или ее подчиненные будут работать правильно.

Возможные значения argv[0] включают:

  • /path/to/executable - абсолютный путь
  • ../bin/executable - относительно pwd
  • bin/executable - относительно pwd
  • ./foo - относительно pwd
  • executable - имя базы, найти в пути
  • 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 систем, выделите, но не отбрасывайте часть префикса hostname /user/drive, если она есть; часть, которая часто предшествует двоеточию или следует после начального "//".

  • Если 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]методы, если они доступны и у вас нет проблем с разрешениями. В некотором маловероятном случае (когда вы рассматриваете все версии всех систем), что они присутствуют и не выходят из строя, они могут быть более авторитетными.

  • Необязательно: проверьте имя пути, переданное с помощью параметра командной строки.

  • Необязательно: проверьте путь в среде, явно переданной вашим сценарием-оболочкой, если таковой имеется.

  • Необязательно: В крайнем случае попробуйте переменную среды "_". Это может указывать на другую программу целиком, например на оболочку пользователя.

  • Разрешить символические ссылки, может быть несколько слоев. Существует возможность бесконечных циклов, хотя, если они существуют, ваша программа, вероятно, не будет запущена.

  • Канонизируйте имя файла, разрешив подстроки типа "/foo/../bar/" в "/bar/". Обратите внимание, что это может изменить значение, если вы пересекаете точку монтирования в сети, поэтому канонизация не всегда хорошая вещь. На сетевом сервере символ ".." в символьной ссылке может использоваться для прохождения пути к другому файлу в контексте сервера, а не на клиенте. В этом случае вы, вероятно, захотите контекст клиента, чтобы канонизация была в порядке. Также конвертируйте шаблоны типа "/./" в "/" и "//" в "/". В ракушке, readlink --canonicalize будет разрешать несколько символических ссылок и канонизировать имя. Чейз может сделать подобное, но не установлен. realpath() или же canonicalize_file_name(), если присутствует, может помочь.

Если realpath() не существует во время компиляции, вы можете позаимствовать копию из разрешительно лицензированного дистрибутива библиотеки и скомпилировать ее самостоятельно, а не изобретать велосипед. Исправьте потенциальное переполнение буфера (укажите размер выходного буфера, подумайте, что strncpy() vs strcpy()), если вы будете использовать буфер меньше, чем PATH_MAX. Возможно, будет проще использовать переименованную личную копию, чем тестировать, если она существует. Разрешительная копия лицензии с android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Помните, что несколько попыток могут быть успешными или частично успешными, и не все они могут указывать на один и тот же исполняемый файл, поэтому рассмотрите возможность проверки вашего исполняемого файла; однако у вас может не быть разрешения на чтение - если вы не можете его прочитать, не рассматривайте это как сбой. Или проверьте что-то рядом с вашим исполняемым файлом, например, каталог "../lib/", который вы пытаетесь найти. У вас может быть несколько версий, упакованных и локально скомпилированных версий, локальных и сетевых версий, а также переносных версий локальных и USB-накопителей и т. Д., И существует небольшая вероятность того, что вы можете получить два несовместимых результата из разных методов определения местоположения. И "_" может просто указывать на неправильную программу.

Программа, использующая execve может намеренно установить argv[0] быть несовместимым с фактическим путем, используемым для загрузки программы и повреждения PATH, "_", pwd и т. д., хотя в общем случае для этого нет особых причин; но это может иметь последствия для безопасности, если у вас есть уязвимый код, который игнорирует тот факт, что ваша среда выполнения может быть изменена различными способами, включая, но не ограничиваясь этим, (chroot, файловая система fuse, жесткие ссылки и т. д.). Возможно для команд оболочки, чтобы установить PATH, но не удается его экспортировать.

Вам не обязательно кодировать для не-Unix систем, но было бы неплохо знать о некоторых особенностях, чтобы вы могли написать код таким образом, чтобы потом было не так сложно его портировать, Помните, что некоторые системы (DEC VMS, DOS, URL-адреса и т. Д.) Могут иметь имена дисков или другие префиксы, которые заканчиваются двоеточием, например "C:\", "sys$drive:[foo]bar" и "file":/// Foo / бар / Баз". Старые системы DEC VMS используют "[" и "]" для включения части пути в каталог, хотя это может измениться, если ваша программа скомпилирована в среде POSIX. Некоторые системы, такие как VMS, могут иметь версию файла (разделенную точкой с запятой в конце). В некоторых системах используются две последовательные косые черты, такие как "// диск / путь / к / файлу" или "пользователь @ хост: / путь / к / файлу" (команда scp) или "файл: // имя хоста / путь / к / файлу" (URL). В некоторых случаях (DOS, windoze) PATH может иметь разные символы-разделители - ";" vs ":" и "\" vs "/" для разделителя пути. В csh/tsh есть "путь" (разделенный пробелами) и "PATH", разделенный двоеточиями, но ваша программа должна получить PATH, поэтому вам не нужно беспокоиться о пути. DOS и некоторые другие системы могут иметь относительные пути, начинающиеся с префикса диска. C:foo.exe ссылается на foo.exe в текущем каталоге на диске C, поэтому вам нужно найти текущий каталог на C: и использовать его для pwd.

Пример символических ссылок и оболочек в моей системе:

/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, которая обрабатывает три основных случая argv[0], Это требует некоторых изменений, хотя:

  • Надо будет переписать все strcat() а также strcpy() использовать strncat() а также strncpy(), Несмотря на то, что переменные объявлены с длиной PATHMAX, входное значение длины PATHMAX-1 плюс длина объединенных строк будет> PATHMAX, а входное значение длины PATHMAX будет не определено.
  • Его нужно переписать как библиотечную функцию, а не просто распечатывать результаты.
    • Он не может канонизировать имена (используйте код реального пути, на который я ссылался выше)
    • Не удается разрешить символические ссылки (используйте код realpath)

Таким образом, если вы комбинируете код HP и код realpath и исправляете их так, чтобы они были устойчивы к переполнению буфера, у вас должно быть что-то, что может правильно интерпретировать argv[0],

Следующее иллюстрирует фактические значения argv[0] для различных способов вызова одной и той же программы в Ubuntu 12.04. И да, программа была случайно названа echoargc вместо echoargv. Это было сделано с использованием сценария для чистого копирования, но выполнение этого вручную в оболочке дает те же результаты (за исключением того, что псевдонимы не работают в сценарии, если вы явно не включили их).

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

Эти примеры иллюстрируют, что методы, описанные в этом посте, должны работать в широком диапазоне обстоятельств и почему необходимы некоторые из шагов.

РЕДАКТИРОВАТЬ: Теперь программа, которая печатает argv[0], была обновлена, чтобы фактически найти себя.

// 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 также корректно находят программу.

Есть одна потенциальная ловушка. access() Функция удаляет разрешения, если программа настроена перед тестированием. Если есть ситуация, когда программа может быть найдена как пользователь с повышенными правами, но не как обычный пользователь, то может быть ситуация, когда эти тесты не пройдут, хотя маловероятно, что программа действительно может быть выполнена при таких обстоятельствах. Вместо этого можно использовать euidaccess(). Однако возможно, что он может найти недоступную программу раньше, чем фактический пользователь.

Познакомьтесь с библиотекой whereami от Gregory Pakosz (в которой всего один файл C); он позволяет вам получить полный путь к текущему исполняемому файлу на различных платформах. В настоящее время он доступен как репозиторий на github здесь.

Альтернатива в 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.

Для надежной работы этой платформы на разных платформах необходимо использовать операторы #ifdef.

Приведенный ниже код находит путь к исполняемому файлу в Windows, Linux, MacOS, Solaris или FreeBSD (хотя 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();

Если бы вам когда-нибудь приходилось поддерживать, скажем, Mac OS X, у которой нет /proc/, что бы вы сделали? Используйте #ifdefs для изоляции кода, специфичного для платформы (например, NSBundle)?

Да, выделение кода для платформы с #ifdefs это обычный способ, которым это сделано.

Другой подход будет иметь иметь чистый #ifdef-less заголовок, который содержит объявления функций и помещает реализации в исходные файлы для конкретной платформы. Например, посмотрите, как библиотека Poco C++ делает что-то похожее для своего класса Environment.

В зависимости от версии QNX Neutrino существуют разные способы найти полный путь и имя исполняемого файла, который использовался для запуска запущенного процесса. Я обозначаю идентификатор процесса как <PID>, Попробуйте следующее:

  1. Если файл /proc/self/exefile существует, то его содержимое является запрашиваемой информацией.
  2. Если файл /proc/<PID>/exefile существует, то его содержимое является запрашиваемой информацией.
  3. Если файл /proc/self/as существует, то:
    1. open() файл.
    2. Выделите буфер, по крайней мере, sizeof(procfs_debuginfo) + _POSIX_PATH_MAX,
    3. Дайте этот буфер в качестве входных данных для devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...,
    4. Приведите буфер к procfs_debuginfo*,
    5. Запрашиваемая информация находится на path поле procfs_debuginfo состав. Предупреждение: по некоторым причинам, иногда QNX пропускает первую косую черту / пути к файлу. Предвидеть что / при необходимости.
    6. Очистить (закрыть файл, освободить буфер и т. Д.).
  4. Попробуйте процедуру в 3. с файлом /proc/<PID>/as,
  5. Пытаться dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo) где dlinfo это Dl_info структура которого dli_fname может содержать запрошенную информацию.

Надеюсь, это поможет.

В дополнение к ответу mark4o FreeBSD также имеет

const char* getprogname(void)

Он также должен быть доступен в MacOS. Он доступен в GNU/Linux через libbsd.

AFAIK, нет такого способа. И есть также двусмысленность: что бы вы хотели получить в качестве ответа, если один и тот же исполняемый файл имеет несколько жестких ссылок, "указывающих" на него? (Жесткие ссылки на самом деле не "указывают", это один и тот же файл, просто в другом месте в иерархии FS.) Как только execve() успешно выполняет новый двоичный файл, вся информация о его аргументах теряется.

Вы можете использовать argv[0] и проанализировать переменную окружения PATH. Посмотрите на: образец программы, которая может найти себя

Конечно, это касается не всех проектов. Все еще, QCoreApplication::applicationFilePath() ни разу не подвел меня за 6 лет разработки C++/Qt.

Конечно, прежде чем пытаться использовать, следует внимательно прочитать документацию:

Предупреждение: В Linux эта функция попытается получить путь из файловой системы /proc. Если это не удается, предполагается, что argv[0] содержит абсолютное имя исполняемого файла. Функция также предполагает, что текущий каталог не был изменен приложением.

Если честно, я думаю, что #ifdef и другие подобные решения вообще не должны использоваться в современном коде.

Я уверен, что существуют и более мелкие кроссплатформенные библиотеки. Пусть они инкапсулируют внутри все эти специфичные для платформы вещи.

Но я хотел бы знать, есть ли удобный способ найти каталог текущего приложения на C/C++ с кроссплатформенными интерфейсами.

Вы не можете этого сделать (по крайней мере, в Linux)

Поскольку исполняемый файл может во время выполнения процесса, на котором он запущен, переименовать (2) путь к своему файлу в другой каталог (той же файловой системы). См. Также системные вызовы (2) и индексный дескриптор (7).

В Linux исполняемый файл может (в принципе) даже удалить (3) сам, вызвав unlink(2). Затем ядро Linux должно сохранять файл выделенным до тех пор, пока на него больше не будет ссылаться ни один процесс. С помощью proc(5) вы можете делать странные вещи (например, переименовать (2), что /proc/self/exe файл и т. д.)

Другими словами, в Linux понятие "каталог текущего приложения" не имеет никакого смысла.

Прочтите также Advanced Linux Programming and Operating Systems: Three Easy Pieces, чтобы узнать больше.

Также обратите внимание на OSDEV для нескольких операционных систем с открытым исходным кодом (включая FreeBSD или GNU Hurd). Некоторые из них предоставляют интерфейс (API), близкий к POSIX.

Рассмотрите возможность использования (с разрешения) кроссплатформенных фреймворков C++, таких как Qt или POCO, возможно, внесите свой вклад в их работу, перенеся их на вашу любимую ОС.

Если вы пишете код под GPL и используете автоинструменты GNU, то переносимым способом, который заботится о деталях во многих ОС (включая Windows и macOS), является gnulib. relocatable-prog модуль.

Всего два цента. Вы можете найти каталог текущего приложения на C/C++ с кроссплатформенными интерфейсами, используя этот код.

void getExecutablePath(char ** path, unsigned int * pathLength)
{
    // Early exit when invalid out-parameters are passed
    if (!checkStringOutParameter(path, pathLength))
    {
        return;
    }

#if defined SYSTEM_LINUX

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    // Return written bytes, indicating if memory was sufficient
    int len = readlink("/proc/self/exe", exePath, PATH_MAX);

    if (len <= 0 || len == PATH_MAX) // memory not sufficient or general error occured
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_WINDOWS

    // Preallocate MAX_PATH (e.g., 4095) characters and hope the executable path isn't longer (including null byte)
    char exePath[MAX_PATH];

    // Return written bytes, indicating if memory was sufficient
    unsigned int len = GetModuleFileNameA(GetModuleHandleA(0x0), exePath, MAX_PATH);
    if (len == 0) // memory not sufficient or general error occured
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_SOLARIS

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    // Convert executable path to canonical path, return null pointer on error
    if (realpath(getexecname(), exePath) == 0x0)
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    unsigned int len = strlen(exePath);
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_DARWIN

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    unsigned int len = (unsigned int)PATH_MAX;

    // Obtain executable path to canonical path, return zero on success
    if (_NSGetExecutablePath(exePath, &len) == 0)
    {
        // Convert executable path to canonical path, return null pointer on error
        char * realPath = realpath(exePath, 0x0);

        if (realPath == 0x0)
        {
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        // Copy contents to caller, create caller ownership
        unsigned int len = strlen(realPath);
        copyToStringOutParameter(realPath, len, path, pathLength);

        free(realPath);
    }
    else // len is initialized with the required number of bytes (including zero byte)
    {
        char * intermediatePath = (char *)malloc(sizeof(char) * len);

        // Convert executable path to canonical path, return null pointer on error
        if (_NSGetExecutablePath(intermediatePath, &len) != 0)
        {
            free(intermediatePath);
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        char * realPath = realpath(intermediatePath, 0x0);

        free(intermediatePath);

        // Check if conversion to canonical path succeeded
        if (realPath == 0x0)
        {
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        // Copy contents to caller, create caller ownership
        unsigned int len = strlen(realPath);
        copyToStringOutParameter(realPath, len, path, pathLength);

        free(realPath);
    }

#elif defined SYSTEM_FREEBSD

    // Preallocate characters and hope the executable path isn't longer (including null byte)
    char exePath[2048];

    unsigned int len = 2048;

    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };

    // Obtain executable path by syscall
    if (sysctl(mib, 4, exePath, &len, 0x0, 0) != 0)
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#else

    // If no OS could be detected ... degrade gracefully
    invalidateStringOutParameter(path, pathLength);

#endif
}

Вы можете посмотреть здесь подробнее.

Более портативный способ получить путь к имени исполняемого образа:

PS может дать вам путь к исполняемому файлу, если у вас есть идентификатор процесса. Также ps - это утилита POSIX, поэтому она должна быть переносимой

поэтому, если идентификатор процесса равен 249297, эта команда дает вам только путь.

    ps -p 24297 -o comm --no-heading

Объяснение аргументов

-p - выбирает данный процесс

-o comm - отображает имя команды (-o cmd выбирает всю командную строку)

--no-heading - не отображать строку заголовка, только вывод.

Программа A C может запустить это через popen.

Я не нашел этого в стандарте, где это написано для работы, но в C++17 и более поздних версиях это работает на всех платформах, которые я тестировал:

      std::filesystem::canonical(argv[0])

Несмотря на тосодержит разные вещи в зависимости от платформы, когда канонизирован, он просто работает. Он дает абсолютный путь к исполняемому файлу, независимо от cwd.

Если вы используете C, вы можете использовать функцию getwd:

int main()
{       
 char buf[4096];
 getwd(buf);
 printf(buf);
}

Это позволит вам распечатать на стандартном выходе текущий каталог исполняемого файла.

Путь абсолютного значения программы находится в PWD envp вашей основной функции, также есть функция в C, называемая getenv, так что это так.

Другие вопросы по тегам