Як отримати каталог, з якого працює програма?


269

Чи існує платформово-агностичний та файлосистемно-агностичний метод для отримання повного шляху до каталогу, звідки працює програма за допомогою C / C ++? Не плутати з поточним робочим каталогом. (Будь ласка, не пропонуйте бібліотеки, якщо вони не є стандартними, як clib або STL.)

(Якщо немає платформного / файлово-агностичного методу, також вітаються пропозиції, що працюють у Windows та Linux для конкретних файлових систем.)


@chakrit: Це було б чудово. (Хоча ця проблема зазвичай не виникає під Windows.)
Ешвін Нанджаппа,

2
Якщо ви не зможете надійно дістати шлях argv[0], методика буде дуже залежною від ОС.
David R Tribble

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

Ось код, щоб отримати повний шлях до виконання програми:

Windows:

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;

3
Я думаю, що це єдина відповідь, яка відповідає на це питання, і робить це як для Windows, так і для Linux. Хороша робота.
Френк Щебра

6
Boo для / 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);

44
Хороша відповідь, але я вважав, що "поточний робочий каталог" - це не те, що хотілося.
Майкл Берр

4
вам слід додати, що навіть якщо деякі документації говорять про те, що cCurrentpath може бути недійсним і буде виділено getcwd, getcwd, схоже, не виділяє щось на Mac OS, і тихо завершує програму
Janusz

4
Існує невелика помилка, але , до жаль , я не можу редагувати ще .. лінія 10: cCurrentpath: має бути cCurrentPath
Lipis

8
IMO для Windows функцій з іменами POSIXy (деякі з яких починаються з підкреслення) слід взагалі уникати. Вони не є справжніми API для Windows, а, скоріше, CRT. API, який ви хочете використовувати, це GetCurrentDirectory (). msdn.microsoft.com/en-us/library/aa364934(VS.85).aspx
asveikau

6
Відповідь Майка правильна. "Поточний каталог" не завжди такий самий, як каталог, з якого працює бінарний файл. Наприклад, якщо програма працює як служба в Windows, поточний каталог, ймовірно, буде C: \ Windows \ System32, тоді як двійковий dir відрізняється.
Lucky Luke

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

2
@ Адріан, я, як правило, не програміст Windows, але чи НЕ ВКАЗАТИ чи десь сказати вашому компілятору автоматично використовувати аромат функції _W ()?
Восьминіг

1
@Octopus: щоб використовувати широкі дзвінки, вам потрібно використовувати WCHAR (замість char) та std :: wstring (замість std :: string).
Адріан Маккарті

29

Якщо ви хочете стандартного способу без бібліотек: Ні. Вся концепція каталогу не включена в стандарт.

Якщо ви погоджуєтесь, що деяка (портативна) залежність від майже стандартної вкладки нормальна: Використовуйте бібліотеку файлових систем Boost і запитайте про початковий_path () .

ІМХО, настільки близький, наскільки ви можете, з хорошою кармою (Boost - це добре встановлений високоякісний набір бібліотек)


8
З Документів Boost: шаблон <class Path> const Path & Initial_path (); Повертає: current_path () під час входу до main (). І current_path () - це як би POSIX getcwd () '. Це не те, що запитував запитувач.
Джонатан Леффлер


Як коментується, це дає шлях від виклику бінарного файлу, а не шлях до двійкового ... як це можна було запустити з іншої папки.
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прапором.

Якщо ви хочете використовувати Filesystem з Microsoft Visual Studio, прочитайте це .


6
З посилання 1-2) Returns the absolute path of the current working directory, obtained as if by POSIX getcwd. (2) returns path() if error occurs. referenced , Downvoted, оскільки ОП спеціально запитує про поточний шлях виконуваного файлу, а не про поточний робочий каталог.
С. Саад

20

Я знаю, що сьогодні дуже пізно відповісти на це, але я виявив, що жодна з відповідей не була для мене такою корисною, як моє власне рішення. Дуже простий спосіб пройти шлях від вашого CWD до папки бін такий:

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

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

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

Я спробував такий підхід в Linux, використовуючи повний шлях, псевдонім тощо, і він працює чудово.

ПРИМІТКА:

Якщо ви перебуваєте у вікні, ви повинні використовувати "\" як роздільник файлів не '/'. Вам також доведеться уникати цього, наприклад:

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

Я думаю, що це має працювати, але не перевірено, тому коментар буде вдячний, якщо він працює, чи виправити, якщо ні.


Так, він працює і в Windows. Я думаю, що це найкраще рішення. Наскільки мені відомо, argv [0] завжди тримає шлях до виконуваного файлу.
Wodzu

4
argv[0]це дуже приємна ідея, але на жаль, я отримую в Linux це "./my_executable_name" або "./make/my_executable_name". В основному те, що я отримую, повністю залежить від того, як я його запускаю
Xeverous

@Xeverous: так що? Якщо у мене є деякі файли відносно мого виконуваного файлу, які його потрібно відкрити, починаючи з "./" або "./make/" у ваших випадках повинні працювати. "." - це поточна робоча директорія, і argv [0] підкаже вам відносний шлях до виконуваного файлу звідти, саме цього і має хотіти ОП. Це в будь-якому випадку саме те, що мені потрібно.
nilo

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]. Але що ще важливіше, що робити, якщо зображення розташоване через PATH? Чи заповнює linux повний шлях або просто те, що є в командному рядку?
Майкл Берр

Як зазначав Майк Б, це не загальне рішення; він працює лише в дуже обмежених обставинах. В основному, лише коли ви запускаєте команду за відносним іменем шляху - і це не все настільки елегантно, коли ви запускаєте ../../../bin/progname замість ./test
Джонатан Леффлер

Якщо ви вирішите можливий відносний шлях argv [0] порівняно з поточним каталогом (оскільки argv [0] може бути "../../myprogram.exe"), це, мабуть, найбезпечніший спосіб відповісти на питання. Він завжди буде працювати і портативний (він працює навіть на Android!).
jpo38

7

Для Win32 GetCurrentDirectory повинен зробити свою справу.


Ця функція є великою : багатопотокові програми та код спільної бібліотеки не повинні використовувати функцію GetCurrentDirectory і уникати використання відносних імен . Якщо ви можете працювати з цим припущенням, то це найкраще рішення.
McLeary

6

Ви не можете використовувати argv [0] для цієї мети, зазвичай він містить повний шлях до виконуваного файлу, але це не обов'язково - процес може бути створений з довільним значенням у полі.

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

У Windows використовуйте GetModuleFileName (), у Linux - файли для читання / dev / proc / procID / ..


3

Просто для запізнілого куча тут ...

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

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


3

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

Приклад:

int main()
{
    system("dir");
    system("pause"); //this wait for Enter-key-press;
    return 0;
}

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);
}

1

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

У Windows ви можете використовувати _getcwd () , як використання getcwd () застаріле.

Для стандартних бібліотек, якби Boost був досить стандартним для вас, я б запропонував Boost :: файлову систему, але вони, здається, видалили нормалізацію шляху з пропозиції. Можливо, доведеться почекати, поки TR2 стане доступним для цілком стандартного рішення.


10
getcwd () не робить того, про що питав запитувач.
Джонатан Леффлер

чи не в тому, що прийнята відповідь використовує getcwd (), чи я не просто розумію?
Snađошƒаӽ

Я підтримав те, що ти саме той, хто прийшов із тим, що вважається правильною відповіддю першим.
Арно

Ця відповідь навіть не намагається вирішити питання. Соромно писати це.
HelloWorld

1

Щодо відносних шляхів, ось що я зробив. Мені відомо вік цього питання, я просто хочу надати більш просту відповідь, яка працює в більшості випадків:

Скажіть, у вас такий шлях:

"path/to/file/folder"

Чомусь з цим добре спрацьовані Linux-файли, виконані в затемненні. Однак, Windows дуже заплутався, якщо йому подати такий шлях!

Як зазначено вище, існує декілька способів дістати поточний шлях до виконуваного файлу, але найпростіший спосіб, який я знаходжу, створює шарм у більшості випадків - додавання його до ФРОНТУ вашого шляху:

"./path/to/file/folder"

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

EDIT: Це не спрацює, якщо ви спробуєте запустити виконуваний файл з коду :: блоків, якщо це середовище розробки, яка використовується, оскільки чомусь код :: блоки не завантажують речі правильно ...: D

EDIT2: Деякі нові речі, які я знайшов, це те, що якщо ви вказали статичний шлях, такий як цей у своєму коді (якщо припустити Example.data - це те, що вам потрібно завантажити):

"resources/Example.data"

Якщо ви запускаєте додаток із фактичного каталогу (або в Windows, ви робите ярлик і встановлюєте робочу діру на свою dir-програму), вона працюватиме так. Майте це на увазі під час налагодження проблем, пов’язаних із відсутніми шляхами до ресурсів / файлів. (Особливо в IDE, які встановлюють неправильний робочий dir при запуску збірки exe з IDE)


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

Як Мінок згадував , немає такої функціональності, визначеної в стандарті C і C ++. Це вважається суто специфічною для ОС функцією, і вона визначена, наприклад, у стандарті POSIX.

Торстен79 дав гарну пропозицію, це бібліотека Boost.Filesystem. Однак це може бути незручно у тому випадку, якщо ви не хочете, щоб у вашій програмі були залежності залежності часу зв’язку у двійковій формі.

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


0

Команда bash bash, яка ім'я , повідомить про шлях до програми.

Навіть якщо хтось може видати команду з вашої програми і направити вихід у файл tmp, а програма згодом прочитає цей файл tmp, він не скаже вам, чи виконується ця програма. Він лише повідомляє вам, де знаходиться програма з таким ім'ям.

Потрібно отримати ідентифікаційний номер процесу та проаналізувати шлях до імені

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



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
}

Хороша відповідь, але не визначена поведінка додавати декларації чи визначення до простору іменstd . Щоб уникнути цього, ви можете додати як простори імен, так std::filesystemі std::experimental::filesystemдо третього простору імен на ваш вибір або просто використовувати using std::filesystem::path, якщо ви не заперечуєте над додаванням декларації pathдо глобальної простори імен.
Кассіо Ренан

Я думаю, що після C ++ 14 експериментальна :: файлова система більше не використовується, тож можна просто забути про це? (переходить у першу гілку #if)
TarmoPikaro
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.