Как получить каталог, из которого запускается программа?

269

Существует ли платформо-независимый и файлово-независимый метод для получения полного пути к каталогу, из которого программа работает с использованием C / C ++? Не путать с текущим рабочим каталогом. (Пожалуйста, не предлагайте библиотеки, если они не стандартные, такие как clib или STL.)

(Если нет метода, независимого от платформы / файловой системы, приветствуются предложения, которые работают в Windows и Linux для конкретных файловых систем.)

Эшвин Нанджаппа
источник
@chakrit: Это было бы здорово. (Хотя эта проблема обычно не возникает под Windows.)
Эшвин Нанджаппа
2
Если вы не сможете надежно извлечь путь argv[0], метод будет сильно зависеть от ОС.
Дэвид Р. Триббл
1
Просто для пояснения: «текущий каталог» или «каталог, из которого запускается программа» (в терминологии вопроса) - это каталог, в котором находится файл образа программы (файл ~ .exe), и «текущий рабочий каталог» - это каталог, который заполняется автоматически, если программа использует относительные пути?
Colemik
3
Когда вы #include <windows.h>, Windows автоматически помещает char*путь к исполняемому файлу в _pgmptr. Вам не нужно вызывать дополнительные функции или предполагать ненужные, если вы работаете только в Windows.
rsethc
1
Несмотря на то, что комментарий сделан три года назад, я хотел бы расширить комментарий rsethc о _pgmptr. MSDN документация утверждает , что _pgmptrи _wpgmptrпеременные являются устаревшими, и вы должны использовать функцию _get_pgmptr(char**)или _get_wpgmptr(wchar_t**)вместо этого. MSDN
Hydranix

Ответы:

181

Вот код, чтобы получить полный путь к исполняемому приложению:

Окна:

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

Linux:

int bytes = MIN(readlink("/proc/self/exe", pBuf, len), len - 1);
if(bytes >= 0)
    pBuf[bytes] = '\0';
return bytes;
Deduplicator
источник
3
Я думаю, что это единственный ответ здесь, который отвечает на вопрос, и делает это как для Windows, так и для Linux. Хорошая работа.
Франк Щерба
6
Boo for / proc / pid / exe - почему-то не поддерживается в OS X
Крис Лутц
24
Когда я вижу код, который смотрит на /procменя, он немного умирает. Весь мир - это не Linux, и даже на этой одной платформе /procследует рассмотреть возможность изменения от версии к версии, от арки к арке и т. Д.
asveikau
4
если они запускаются с использованием команды псевдонима в Linux, argv [0] - это «имя команды» или расширен?
Энди Дент
20
Как насчет добавить, char pBuf[256]; size_t len = sizeof(pBuf);чтобы дать решение более четко.
charles.cc.hsu
166

Если вы извлекаете текущий каталог при первом запуске вашей программы, то вы фактически получаете каталог, из которого была запущена ваша программа. Сохраните значение в переменной и обратитесь к нему позже в вашей программе. Это отличается от каталога, в котором находится текущий исполняемый файл программы . Это не обязательно тот же каталог; если кто-то запускает программу из командной строки, то программа запускается из текущего рабочего каталога командной строки, даже если файл программы находится в другом месте.

getcwd - это функция POSIX, которая поддерживается всеми POSIX-совместимыми платформами. Вам не нужно было бы делать ничего особенного (кроме указания правильных заголовков unistd.h в Unix и direct.h в Windows).

Поскольку вы создаете программу на C, она будет связываться со стандартной библиотекой времени выполнения c, к которой привязаны ВСЕ процессы в системе (исключены специально созданные исключения), и будет включать эту функцию по умолчанию. CRT никогда не считается внешней библиотекой, поскольку она обеспечивает базовый стандартный интерфейс, совместимый с ОС.

В Windows функция getcwd устарела в пользу _getcwd. Я думаю, что вы могли бы использовать это таким образом.

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

 char cCurrentPath[FILENAME_MAX];

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

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

printf ("The current working directory is %s", cCurrentPath);
computinglife
источник
44
Хороший ответ, но я подумал, что «текущий рабочий каталог» - это не то, что нужно.
Майкл Берр
4
Вы должны добавить, что даже если в некоторых документах говорится, что cCurrentpath может быть нулевым и будет выделен getcwd, getcwd, похоже, не выделяет что-то в Mac OS и тихо вылетает из программы
Janusz
4
Существует небольшая ошибка, но , к сожалению , я не могу редактировать еще .. линия 10: cCurrentpath: должно быть cCurrentPath
Lipis
8
IMO в Windows: следует избегать именованных функций POSIXy (некоторые из которых начинаются с подчеркивания). Это не настоящие Windows API, а CRT. API-интерфейс Windows, который вы хотите использовать, - это GetCurrentDirectory (). msdn.microsoft.com/en-us/library/aa364934(VS.85).aspx
asveikau
6
Майк ответит правильно. «Текущий каталог» не всегда совпадает с каталогом, из которого запускается двоичный файл. Например, если приложение работает как служба в Windows, текущим каталогом, вероятно, будет C: \ Windows \ System32, тогда как двоичный каталог отличается.
Счастливчик Люк
42

Это с форума cplusplus

На окнах:

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

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

В Linux:

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

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

На HP-UX:

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

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

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

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

  return std::string( result );
}
Осьминог
источник
1
Это решение Windows не будет обрабатывать не-ANSI символы в пути. Вы, вероятно, должны использовать GetModuleFileNameW и явно преобразовать его в UTF-8 (будьте осторожны, чтобы преобразовать его обратно всякий раз, когда вам нужно будет выполнить команду файловой системы).
Адриан Маккарти
3
Для решения Windows я получаю ошибку error: cannot convert 'char*' to 'LPWCH {aka wchar_t*}' for argument '2' to 'DWORD GetModuleFileNameW(HMODULE, LPWCH, DWORD)'при компиляции с MinGW.
Здравствуйте, до свидания
2
@Adrian, я вообще не программист Windows, но разве нет DEFINE или как-то еще, чтобы сказать вашему компилятору, чтобы он автоматически использовал функцию _W ()?
Осьминог
1
@Octopus: чтобы использовать широкие вызовы, вам нужно использовать WCHAR (вместо char) и std :: wstring (вместо std :: string).
Адриан Маккарти
29

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

Если вы согласны, что некоторая (переносимая) зависимость от почти стандартной библиотеки в порядке: используйте библиотеку файловой системы Boost и запросите initial_path () .

ИМХО, это как можно ближе, с хорошей кармой (Boost - это устоявшийся высококачественный набор библиотек)

Thorsten79
источник
8
Из документов Boost: template <class Path> const Path & initial_path (); Возвращает: current_path () во время входа в main (). И current_path () - это «как если бы POSIX getcwd ()». Это не то, что просил спрашивающий.
Джонатан Леффлер
см. boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/… для надстройки 1.46.1
моала
Как уже отмечалось, это дает путь, откуда был вызван двоичный файл, а не путь к двоичному файлу ... так как он может быть запущен из другой папки.
jpo38
21

Файловая система TS теперь является стандартом (и поддерживается gcc 5.3+ и clang 3.9+), поэтому вы можете использовать current_path()функцию из нее:

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

В gcc (5.3+) для включения файловой системы вам нужно использовать:

#include <experimental/filesystem>

и свяжите свой код с -lstdc++fsфлагом.

Если вы хотите использовать файловую систему с Microsoft Visual Studio, прочитайте это .

Marqin
источник
6
По ссылочной ссылке Downvoted, 1-2) Returns the absolute path of the current working directory, obtained as if by POSIX getcwd. (2) returns path() if error occurs. поскольку OP специально спрашивает о текущем пути исполняемого файла, а не о текущем рабочем каталоге.
С. Саад
20

Я знаю, что уже слишком поздно, чтобы дать ответ на этот вопрос, но я обнаружил, что ни один из ответов не был настолько полезен для меня, как мое собственное решение. Очень простой способ получить путь от вашего CWD до вашей папки bin выглядит следующим образом:

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

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

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

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

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

Я попробовал этот подход на Linux, используя полный путь, псевдоним и т. Д., И он работает просто отлично.

НОТА:

Если вы находитесь в Windows, вы должны использовать «\» в качестве разделителя файлов, а не «/». Вам тоже придется избежать этого, например:

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

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

Сэм Редуэй
источник
Да, это работает и на Windows. Я думаю, что это лучшее решение. Насколько я знаю, argv [0] всегда сохраняет путь к исполняемому файлу.
Водзу
4
argv[0]Это очень хорошая идея, но, к сожалению, я получаю в Linux "./my_executable_name" или "./make/my_executable_name". В основном то, что я получу, полностью зависит от того, как я его запустил
Xeverous
1
@Xeverous: ну и что? Если у меня есть файлы, относящиеся к моему исполняемому файлу, которые он должен открыть, начиная с «./» или «./make/», в ваших случаях должно работать. "" является текущим рабочим каталогом, и argv [0] скажет вам относительный путь к исполняемому файлу оттуда, который именно то, что должен хотеть OP. Это в любом случае именно то, что мне нужно.
Нило
9

Нет, стандартного способа нет. Я считаю, что стандарты C / C ++ даже не учитывают наличие каталогов (или других организаций файловой системы).

В Windows метод GetModuleFileName () возвращает полный путь к исполняемому файлу текущего процесса, если для параметра hModule установлено значение NULL . Я не могу помочь с Linux.

Также вы должны уточнить, хотите ли вы текущий каталог или каталог, в котором находится образ программы / исполняемый файл. В этом смысле ваш вопрос немного двусмысленен в этом вопросе.

Майкл Берр
источник
9

В Windows самый простой способ - использовать _get_pgmptrфункцию, stdlib.hчтобы получить указатель на строку, которая представляет абсолютный путь к исполняемому файлу, включая имя исполняемого файла.

char* path;
_get_pgmptr(&path);
printf(path); // Example output: C:/Projects/Hello/World.exe
Адам Яксли
источник
8

Может быть, объединить текущий рабочий каталог с argv [0]? Я не уверен, что это будет работать в Windows, но это работает в Linux.

Например:

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

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

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

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

    return 0;
}

При запуске выдает:

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

Джереми Рутен
источник
Вам понадобится проверить, указан ли абсолютный путь в argv [0]. Но что более важно, что, если изображение находится через ПУТЬ? Линукс заполняет полный путь или только то, что в командной строке?
Майкл Берр
Как указал Майк Б., это необщее решение; это работает только в некоторых очень ограниченных обстоятельствах. В основном, только когда вы запускаете команду по относительному пути - и это не так уж и элегантно, когда вы запускаете ../../../bin/progname вместо ./test
Джонатан Леффлер,
Если вы разрешите возможный относительный путь argv [0] по сравнению с текущим каталогом (поскольку argv [0] может быть "../../myprogram.exe"), это, вероятно, самый безопасный способ ответить на вопрос. Он всегда будет работать и будет переносимым (даже на Android!).
jpo38
7

Для Win32 GetCurrentDirectory должен сделать свое дело.

Torbjörn Gyllebring
источник
Эта функция очень важна : многопоточные приложения и код совместно используемой библиотеки не должны использовать функцию GetCurrentDirectory и должны избегать использования относительных путей . Если вы можете работать с этим предположением, то это лучшее решение.
Маклири
6

Вы не можете использовать argv [0] для этой цели, обычно он содержит полный путь к исполняемому файлу, но необязательно - процесс может быть создан с произвольным значением в поле.

Также обратите внимание, что текущий каталог и каталог с исполняемым файлом - это две разные вещи, поэтому getcwd () вам тоже не поможет.

В Windows используйте GetModuleFileName (), в Linux читайте файлы / dev / proc / procID / ...

eugensk
источник
3

Просто, чтобы запоздало навалить здесь ...

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

Кроме того, вам нужен не текущий рабочий каталог, а каталог, в котором запускается программа, который должен учитывать, как программа попала туда, где она находится - то есть была ли она порождена как новый процесс с помощью форка и т. д. Чтобы получить каталог, в котором запущена программа, как показали решения, требуется, чтобы вы получали эту информацию из структур управления процессами рассматриваемой операционной системы, которая является единственным авторитетом в этом вопросе. Таким образом, по определению, это решение для конкретной ОС.

Minok
источник
3

Для системы Windows на консоли вы можете использовать dirкоманду system ( ). И консоль дает вам информацию о каталоге и т. Д. Читайте о dirкоманде на cmd. Но для Unix-подобных систем я не знаю ... Если эта команда запущена, прочитайте команду bash. lsне отображать каталог ...

Пример:

int main()
{
    system("dir");
    system("pause"); //this wait for Enter-key-press;
    return 0;
}
Alexey1993
источник
2
#include <windows.h>
using namespace std;

// The directory path returned by native GetCurrentDirectory() no end backslash
string getCurrentDirectoryOnWindows()
{
    const unsigned long maxDir = 260;
    char currentDir[maxDir];
    GetCurrentDirectory(maxDir, currentDir);
    return string(currentDir);
}
freezotic
источник
1

На платформах POSIX вы можете использовать getcwd () .

В Windows вы можете использовать _getcwd () , так как использование getcwd () устарело.

Для стандартных библиотек, если бы Boost был достаточно стандартным для вас, я бы предложил Boost :: filesystem, но, похоже, они удалили нормализацию пути из предложения. Возможно, вам придется подождать, пока TR2 не станет легко доступным для полностью стандартного решения.

Fruny
источник
10
getcwd () не делает то, что спросил спрашивающий.
Джонатан Леффлер
не в том, что принятый ответ использует getcwd (), или я не просто понимаю?
Снаđошƒаӽ
Я проголосовал, потому что вы тот, кто пришел с тем, что считается правильным ответом первым.
Арно
Этот ответ даже не пытается ответить на вопрос. Стыдно писать это.
HelloWorld
1

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

Скажем, у вас есть такой путь:

"path/to/file/folder"

По какой-то причине встроенные в Linux исполняемые файлы, созданные в Eclipse, прекрасно с этим работают. Тем не менее, окна становятся очень запутанными, если использовать такой путь для работы!

Как указывалось выше, есть несколько способов получить текущий путь к исполняемому файлу, но самый простой способ, который я нахожу, работает в большинстве случаев - это добавление к ПЕРЕДНЕМУ вашего пути:

"./path/to/file/folder"

Просто добавив "./", вы получите сортировку! :) Затем вы можете начать загрузку из любой директории, которую пожелаете, при условии, что она выполняется с самим исполняемым файлом.

РЕДАКТИРОВАТЬ: Это не будет работать, если вы попытаетесь запустить исполняемый файл из code :: blocks, если используется среда разработки, так как по какой-то причине code :: blocks не загружает вещи правильно ...: D

РЕДАКТИРОВАТЬ 2: Некоторые новые вещи, которые я обнаружил, заключаются в том, что если вы указываете статический путь, такой как этот, в своем коде (при условии, что вам нужно загрузить Example.data):

"resources/Example.data"

Если вы затем запускаете свое приложение из фактического каталога (или в Windows вы делаете ярлык и устанавливаете рабочий каталог в каталог вашего приложения), то оно будет работать так. Помните об этом при отладке проблем, связанных с отсутствующими путями к ресурсам / файлам. (Особенно в IDE, которые устанавливают неправильный рабочий каталог при запуске исполняемого файла сборки из IDE)

FuzzyQuills
источник
1

Решение для библиотеки (хотя я знаю, что об этом не просили). Если вы используете Qt: QCoreApplication::applicationDirPath()

Иоахим
источник
1

Просто мои два цента, но не работает ли следующий код в C ++ 17?

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main(int argc, char* argv[])
{
    std::cout << "Path is " << fs::path(argv[0]).parent_path() << '\n';
}

Кажется, у меня работает на Linux по крайней мере.

Исходя из предыдущей идеи, теперь у меня есть:

std::filesystem::path prepend_exe_path(const std::string& filename, const std::string& exe_path = "");

С внедрением:

fs::path prepend_exe_path(const std::string& filename, const std::string& exe_path)
{
    static auto exe_parent_path = fs::path(exe_path).parent_path();
    return exe_parent_path / filename;
}

И трюк инициализации в main() :

(void) prepend_exe_path("", argv[0]);

Спасибо @Sam Redway за идею argv [0]. И, конечно, я понимаю, что C ++ 17 не существовал много лет, когда ОП задал вопрос.

Нило
источник
0

Повысьте файловую систему initial_path()ведет себя как POSIX getcwd(), и не делает то, что вы хотите, но добавляяargv[0] одна из них к любой из них должно сделать это.

Вы можете заметить, что результат не всегда хорош - вы можете получить такие вещи, как /foo/bar/../../baz/a.outили/foo/bar//baz/a.out , но я считаю, что это всегда приводит к правильному пути с именем исполняемого файла (обратите внимание, что последовательные косые черты в пути свернуты в единицу).

Ранее я писал решение, используя envp(третий аргумент, main()который работал в Linux, но не казался работоспособным в Windows, поэтому я, по сути, рекомендую то же решение, что и кто-то другой ранее, но с дополнительным объяснением того, почему оно действительно правильно даже если результаты не красивые.

Джон Цвинк
источник
0

Как минок упоминал , в стандарте i C или стандарте C ++ такой функциональности нет. Это считается чисто ОС-особенностью, и это указано, например, в стандарте POSIX.

Thorsten79 дал хорошее предложение, это библиотека Boost.Filesystem. Тем не менее, это может быть неудобно, если вы не хотите иметь какие-либо зависимости времени соединения в двоичной форме для вашей программы.

Хорошая альтернатива, которую я бы порекомендовал, - это коллекция библиотек STLSoft C ++, предназначенных только для заголовков, только для заголовков. Мэтью Уилсон (автор обязательных для чтения книг о C ++). Портативный фасад PlatformSTL предоставляет доступ к системно-зависимому API: WinSTL для Windows и UnixSTL в Unix, поэтому это портативное решение. Все системные элементы определяются с использованием признаков и политик, поэтому это расширяемая структура. Конечно, есть библиотека файловой системы.

mloskot
источник
0

Команда Linux Bash, которая Progname , сообщит путь к программе.

Даже если кто-то может выполнить команду which из вашей программы и направить вывод в файл tmp, а затем программа прочитает этот файл tmp, он не сообщит вам, выполняется ли эта программа. Он только говорит вам, где находится программа с таким именем.

Требуется получить номер вашего процесса и разобрать путь к имени.

В моей программе я хочу знать, была ли программа выполнена из каталога bin пользователя или из другого каталога по пути или из / usr / bin. / usr / bin будет содержать поддерживаемую версию. Я чувствую, что в Linux есть единственное решение, которое является переносимым.

Лесли Сатенштейн
источник
0

Используйте realpath()в stdlib.hследующем образом:

char *working_dir_path = realpath(".", NULL);
Манабу Наказава
источник
0

Работает с C ++ 11, используя экспериментальную файловую систему, и C ++ 14-C ++ 17, а также используя официальную файловую систему.

application.h:

#pragma once

//
// https://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros
//
#ifdef __cpp_lib_filesystem
#include <filesystem>
#else
#include <experimental/filesystem>

namespace std {
    namespace filesystem = experimental::filesystem;
}
#endif

std::filesystem::path getexepath();

application.cpp:

#include "application.h"
#ifdef _WIN32
#include <windows.h>    //GetModuleFileNameW
#else
#include <limits.h>
#include <unistd.h>     //readlink
#endif

std::filesystem::path getexepath()
{
#ifdef _WIN32
    wchar_t path[MAX_PATH] = { 0 };
    GetModuleFileNameW(NULL, path, MAX_PATH);
    return path;
#else
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
#endif
}
TarmoPikaro
источник
Хороший ответ, но это неопределенное поведение для добавления объявлений или определений в пространство именstd . Чтобы избежать этого, вы можете добавить как пространства имен, так std::filesystemи std::experimental::filesystemтретье пространство имен по вашему выбору, или просто использовать using std::filesystem::path, если вы не против добавить объявление pathв глобальное пространство имен.
Кассио Ренан
Я думаю, что после C ++ 14 экспериментальная :: файловая система больше не используется, так что вы можете просто забыть об этом? (идет в первую ветку #if)
TarmoPikaro