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.