Поиск пути к текущему исполняемому файлу без / proc / self / exe

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

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


person Uros Dimitrijevic    schedule 21.06.2009    source источник
comment
Дубликат: stackoverflow.com/questions/933850/   -  person Greg Hewgill    schedule 27.10.2009
comment
Я погуглил: возьми мой ps -o comm. Что привело меня сюда: /proc/pid/path/a.out   -  person basin    schedule 28.03.2013
comment
IMHO ответ prideout заслуживает быть на вершине , потому что он правильно отвечает требованиям кросс-платформенных интерфейсов и очень прост в интеграции.   -  person Stéphane Gourichon    schedule 14.09.2018
comment
В C ++ 17 вы можете использовать std::current_path(). Я не вижу переносимого метода в стиле C, поскольку разные ОС используют разные строковые форматы, например wchar_t, UTF-8 и т. Д.   -  person ALX23z    schedule 16.09.2020
comment
@ ALX23z std::filesystem::current_path() не указывает путь к текущему исполняемому файлу. Это даст вам текущий рабочий каталог.   -  person Ted Lyngmo    schedule 16.09.2020
comment
@TedLyngmo, обычно это одно и то же ... но да.   -  person ALX23z    schedule 16.09.2020
comment
@ ALX23z Нет, текущий каталог никогда не совпадает с исполняемым файлом.   -  person Ted Lyngmo    schedule 16.09.2020


Ответы (14)


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

Существуют также сторонние библиотеки, которые можно использовать для получения этой информации, например whereami, как указано в ответе prideout, или если вы используете Qt, QCoreApplication :: applicationFilePath (), как указано в комментарии.

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

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

person mark4o    schedule 21.06.2009
comment
Спасибо @stepancheg; добавлен. Однако похоже, что procfs по умолчанию не монтируется во FreeBSD, поэтому я связался с другим вопросом, касающимся sysctl. - person mark4o; 10.09.2009
comment
Также обратите внимание, что _NSGetExecutablePath () не следует символическим ссылкам. - person naruse; 08.08.2011
comment
NetBSD: ссылка для чтения / proc / curproc / exe DragonFly BSD: ссылка для чтения / proc / curproc / file - person naruse; 08.08.2011
comment
Солярис: char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; это отличается от getexecname() - что эквивалентно pargs -x <PID> | grep AT_SUN_EXECNAME ... - person FrankH.; 19.06.2012
comment
Если вы умеете использовать Qt, они это рассмотрели: QDesktopServices::storageLocation(QDesktopServices::DataLocation). См. qt-project.org/doc/qt-4.8/qdesktopservices.html - person Dan Nissenbaum; 05.05.2013
comment
QDesktopServices :: storageLocation (QDesktopServices :: DataLocation) Это не путь к исполняемому файлу, это путь к пользовательскому каталогу, в котором должны храниться данные. - person ; 13.08.2013
comment
QCoreApplication :: applicationFilePath () может использоваться для приложений на основе Qt (4.x, 5.x), которые согласно документации абстрагируют интерфейсы, перечисленные в ответе. - person cybevnm; 13.06.2015
comment
OpenBSD - единственный, где вы все еще не можете этого сделать в 2017 году. Вы должны использовать путь PATH и argv [0] - person Lothar; 25.04.2017
comment
Также Solaris: /proc/self/path/a.out. Не уверен, какие версии самые старые, чтобы поддерживать это. - person Lassi; 19.03.2020
comment
@ mark4o: Вы не возражаете, если я отредактирую ваш ответ, чтобы указать на ответ prideout, который показывает, как этого добиться с помощью доступной библиотеки? - person krlmlr; 19.09.2020
comment
@krlmlr: добавлен вместе с Qt, как указано выше. - person mark4o; 20.09.2020

Использование /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, HP-UX, 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, отделите, но не отбрасывайте часть префикса пути host / 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 разрешит несколько символических ссылок и канонизирует имя. Chase может сделать то же самое, но он не установлен. 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 и Windows) 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

Обратите внимание, что пользователь счёт разместил ссылка выше на программу в HP, которая обрабатывает три основных случая argv[0]. Тем не менее, он нуждается в некоторых изменениях:

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

Итак, если вы объедините код 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"

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

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

person whitis    schedule 14.12.2015
comment
Вы приложили много усилий - молодец. К сожалению, ни strncpy(), ни (особенно) strncat() безопасно не используются в коде. strncpy() не гарантирует нулевое завершение; если исходная строка длиннее целевого пространства, строка не оканчивается нулем. strncat() очень сложно использовать; strncat(target, source, sizeof(target)) неверно (даже если target - пустая строка для начала), если source длиннее целевого. Длина - это количество символов, которые можно безопасно добавить к цели, за исключением конечного нуля, поэтому sizeof(target)-1 является максимальным. - person Jonathan Leffler; 21.12.2015
comment
Код strncpy правильный, в отличие от метода, который, по вашему мнению, я должен использовать. Предлагаю вам внимательно прочитать код. Он не переполняет буферы и не оставляет их незавершенными. Каждое использование strncpy () / stncat () ограничивается копированием sizeof (buffer), что является допустимым, а затем последний символ буфера заполняется нулем, перезаписывая последний символ буфера. Однако strncat () неправильно использует параметр размера в качестве счетчика и может переполниться из-за того, что он предшествовал атакам переполнения буфера. - person whitis; 27.03.2017
comment
sudo apt-get install libbsd0 libbsd-dev, затем s / strncat / strlcat / - person whitis; 27.03.2017
comment
Не используйте PATH_MAX. Это перестало работать 30 лет назад, всегда используйте malloc. - person Lothar; 25.04.2017
comment
Также, если вы используете вызов init. Полностью разрешите путь к исполняемому файлу при инициализации, а не только его часть, а затем сделайте это позже при вызове. Здесь невозможно ленивое вычисление, если вы используете в преобразователе realpath. Вместе с другими эроррами просто худший код, который я видел в длинном ответе на stackoverflow. - person Lothar; 25.04.2017

Библиотека whereami Грегори Пакоша реализует это для различных платформ, используя API, упомянутые в сообщении mark4o. Это наиболее интересно, если вам просто нужно решение, которое работает для портативного проекта, и вас не интересуют особенности различных платформ.

На момент написания поддерживаемые платформы:

  • Окна
  • Linux
  • Mac
  • iOS
  • Android
  • QNX Neutrino
  • FreeBSD
  • NetBSD
  • DragonFly BSD
  • SunOS

Библиотека состоит из 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;
}
person prideout    schedule 26.09.2015

В 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.

person Dolda2000    schedule 13.05.2014
comment
Мне это нравится, потому что это просто, и glibc в любом случае включен в Gtk + (который я использую). - person Colin Keenan; 20.03.2017
comment
если я начну свой процесс с ./build/foo, ваше решение вернет: ./build/foo, что совершенно бесполезно. (дебиан яблочко). - person BitTickler; 07.06.2021
comment
@BitTickler: Это так? Я бы сказал, что наиболее распространенное использование для поиска исполняемого файла - это доступ к файлам относительно добавления, и нет проблем с относительным путем. И если вам по какой-либо причине нужен абсолютный путь, вы не можете абсолютизировать его. - person Dolda2000; 07.06.2021
comment
Моим вариантом использования было приложение gtk с некоторыми файлами ресурсов. Пытаясь запустить его через избранное gnome (не знаю, какой у них текущий каталог ...), мне нужно было построить абсолютный путь к моим ресурсам (которые находятся на одну папку выше моего каталога сборки meson с исполняемым файлом ...). Итак, если я не знаю, что такое ., я фактически не могу абсолютизировать путь ... В итоге я использовал решение readlink, которое возвращает абсолютный путь. - person BitTickler; 08.06.2021
comment
@BitTickler: Вы всегда знаете, что такое ., просто позвоните getcwd(). Однако даже если вы этого не сделали, и вам просто нужно подняться на уровень каталога, всегда есть .. - person Dolda2000; 08.06.2021

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

Да, изоляция кода, специфичного для платформы, с помощью #ifdefs - это обычный способ сделать это.

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

Например, посмотрите, как библиотека C ++ POCO (переносимые компоненты) делает нечто подобное для их Environment класс.

person StackedCrooked    schedule 21.06.2009

Чтобы эта работа надежно работала на разных платформах, необходимо использовать операторы #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();
person jtbr    schedule 21.10.2015
comment
@ Фрэнк, не понимаю, почему ты так говоришь. Работает для меня. Я видел другой ответ, в котором утверждается, что вам нужен root для доступа к / proc / self / exe, но я не обнаружил этого ни в одной из систем Linux, которые я пробовал (CentOS или Mint). - person jtbr; 12.02.2016

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

  1. Если файл /proc/self/exefile существует, то его содержимое является запрошенной информацией.
  2. Если файл /proc/<PID>/exefile существует, то его содержимое является запрошенной информацией.
  3. If the file /proc/self/as exists, then:
    1. open() the file.
    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 которой может содержать запрошенную информацию.

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

person Dr. Koutheir Attouchi    schedule 13.11.2015
comment
отличный комментарий, но, к сожалению, не работает вообще (QNX 6.5.0), используйте '__progname' как extern - person Alexander G.; 07.10.2020
comment
Эта процедура работает для QNX 6.5 и 6.6 на ARMv7-A. Вы могли что-то упустить. - person Dr. Koutheir Attouchi; 08.10.2020

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

const char* getprogname(void)

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

person Tobias Fendin    schedule 11.09.2020
comment
Он доступен, но не возвращает полный или даже относительный путь из cwd. - person vy32; 27.06.2021

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

Как только execve() успешно выполняет новый двоичный файл, вся информация об аргументах исходной программы теряется.

person zvrba    schedule 21.06.2009
comment
Когда execve () успешно выполняет новый двоичный файл, вся информация о его аргументах теряется. На самом деле аргументы argp и envp не теряются, они передаются как argv [] и окружение, а в некоторых UN * X аргумент пути или что-то из него передается вместе с argp и envp (OS X / iOS, Solaris) или доступны через один из механизмов, перечисленных в ответе mark4o. Но да, это просто дает вам одну из жестких ссылок, если их больше одной. - person ; 12.08.2013

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

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

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

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

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

person Leontyev Georgiy    schedule 11.09.2020

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

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

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

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

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

Прочтите также Расширенное программирование Linux и Операционные системы: три простых элемента, чтобы узнать больше.

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

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

person Basile Starynkevitch    schedule 12.09.2020
comment
Итак, если у вас есть файлы ресурсов относительно вашего исполняемого файла, вы жестко кодируете пути к ресурсам в своем коде? :) Даже если существуют некоторые (довольно неясные) варианты использования, когда процесс запускает что-то, что не было получено из какого-либо файла по любому пути, обычный вариант использования заключается в том, что вам нужен абсолютный путь к вашему исполняемому файлу, чтобы найти другие вещи, расположен относительно него. - person BitTickler; 08.06.2021

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

person Reuben Thomas    schedule 30.01.2021
comment
В документации модуля говорится: В каждой программе добавьте к main в качестве первого оператора (даже до установки локали или выполнения каких-либо действий, связанных с libintl): set_program_name (argv[0]); Прототип этой функции находится в progname.h.. Это означает, что требуется вмешательство программиста. Непонятно, что задумано. - person Jonathan Leffler; 01.02.2021
comment
@JonathanLeffler Ты должен позвонить set_program_name, да. Не уверен, что вы имеете в виду, не совсем ясно, что было задумано. - person Reuben Thomas; 01.02.2021


Всего два цента. Вы можете найти каталог текущего приложения на 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
}

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

person Fatih Şennik    schedule 12.09.2020
comment
Что касается части вашего кода FREEBSD: я думаю, что getprogname() - более идиоматический способ сделать это. (#include <stdlib.h>, это часть libc). - person BitTickler; 08.06.2021