Cross-Platform Home Directory in C++
To get the HOME directory at runtime (meaning it cannot be determined at compile-time, so it cannot be stored as a constant in a header), you may use getenv
(or on Windows, _wgetenv
, since paths should be Unicode-aware and therefore use the wide API on Windows).
POSIX
You may assume the path is specified using the HOME
environment variable.
#include <cstdlib>
#include <string>
std::string get_home()
{
char *dir = getenv("HOME");
if (dir != nullptr) {
return std::string(dir);
} else {
// home not defined, root user, maybe return "/root"
// this code path should generally **not** occur.
return std::string("/");
}
}
Windows
A simple solution, as suggested by Miles Budnek, is to use GetUserProfileDirectory
function.
#include <windows.h>
#include <string>
std::wstring get_home()
{
DWORD size = 0;
HANDLE token = GetCurrentProcessToken();
// get the size needed for the buffer.
GetUserProfileDirectoryW(token, NULL, &size);
if (size == 0) {
throw std::runtime_error("Unable to get required size.\n");
}
// this won't work pre-C++11, since strings weren't guaranteed
// to be continuous
std::wstring home(size, 0);
if (!GetUserProfileDirectoryW(token, &home[0], &size)) {
throw std::runtime_error(("Unable to get home directory.\n");
}
return home;
}
If you would like to rely on environment variables, this is not so easy, but the best solution is to check USERPROFILE
, then HOME
, then HOMEDRIVE
and HOMEPATH
, and if none of those are set, then SystemDrive
as a fallback. This works out to:
#include <cstdlib>
#include <stdexcept>
#include <string>
std::wstring get_home()
{
// check USERPROFILE
wchar_t *home = _wgetenv(L"USERPROFILE");
if (home != nullptr) {
return std::wstring(home);
}
// check HOME
home = _wgetenv(L"HOME");
if (home != nullptr) {
return std::wstring(home);
}
// combine HOMEDRIVE and HOMEPATH
wchar_t *drive = _wgetenv(L"HOMEDRIVE");
wchar_t *path = _wgetenv(L"HOMEPATH");
if (drive != nullptr && path != nullptr) {
// "c:", "\users\{user}"
return std::wstring(drive) + std::wstring(path);
}
// try SystemDrive
home = _wgetenv(L"SystemDrive");
if (home != nullptr) {
return std::wstring(home);
} else {
return std::wstring(L"c:");
}
}
Why not WordExp?
wordexp
is not guaranteed to be a part of Windows compilers, and won't work well on Windows. Also, HOME
is not guaranteed to be set on Windows. You should use (_w)getenv
. Also, wordexp
does shell expansion, which means many other symbols (including *
, character sets, and other environment variables) will be expanded, which may not be desired. This is simple, cross-platform, and limited in scope.