-4

I want to store the absolute path of a folder in an hpp file; the path is stored in a public class. I tried using: static constexpr const char* FOLDER_PATH = "$HOME/catkin_ws/src/abc/pqr/xyz"

But, that $HOME seems to be not working. Can I get a workaround that $HOME? If I write /home/myname/ it seems to be working fine. I do not want to write /home/myname/; the issue is I need to change every time I run that code on different systems. I do not want to edit every time; the folder structure remains the same.

Mat_python
  • 151
  • 2
  • 2
  • 10
  • 2
    `$HOME` gets substituted by the SHELL, not the programs and they happen **before** you start the program. However, with [wordexp](https://linux.die.net/man/3/wordexp) you can do it in C and C++. But this function is not part of the standard library, is a POSIX function. – Pablo Jan 16 '18 at 01:28
  • 1
    Possible duplicate of [How do I achieve tilde expansion in C?](https://stackoverflow.com/questions/15991241/how-do-i-achieve-tilde-expansion-in-c) – Pablo Jan 16 '18 at 01:28
  • http://www.cplusplus.com/reference/cstdlib/getenv/ – drescherjm Jan 16 '18 at 01:32
  • All those answers are bad though. You don't need shell expansion to get the home directory, and `wordexp` doesn't work on Windows. There's a better answer. – Alex Huszagh Jan 16 '18 at 01:40

1 Answers1

1

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.

Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67
  • 1
    Windows also has the [`GetUserProfileDirectory`](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762280.aspx) function. – Miles Budnek Jan 16 '18 at 02:02
  • @MilesBudnek That is remarkably simpler. And XP-compatible. Thank you. Editing now. – Alex Huszagh Jan 16 '18 at 02:03
  • Actually, wait, @MilesBudnek, how do I get the token for the currently logged-on user without attempting to log-on a user? – Alex Huszagh Jan 16 '18 at 02:06
  • 1
    You can use [`OpenProcessToken`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa379295.aspx) (and [`GetCurrentProcess`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683179.aspx)) to get a token for the user under which the process is running. – Miles Budnek Jan 16 '18 at 02:11
  • Oh, or [`GetCurrentProcessToken`](https://msdn.microsoft.com/en-us/library/windows/desktop/mt643211.aspx), I guess that's a bit simpler. – Miles Budnek Jan 16 '18 at 02:20
  • Thanks, @AlexanderHuszagh. I am using Ubuntu; I just edited the question and details. Please check. – Mat_python Jan 16 '18 at 21:03
  • @Mat_python Then you want the first example, since Ubuntu is a POSIX-like OS. It **cannot** and **should not** be done at compile-time, you want it at run-time. If you would like, you can define an `extern const std::string` to store a global variable that is initialized when the program starts up, and that variable can be accessed from a header. – Alex Huszagh Jan 16 '18 at 22:24
  • @AlexanderHuszagh Is there a way to do with `static constexpr const char* FOLDER_PATH` and not `extern const std::string`? Thank you. – Mat_python Jan 17 '18 at 00:55
  • @Mat_python. No. By definition, no. Absolutely, completely, utterly impossible, and even if it **was** possible, you **would** not want to do that. Imagine only ever being able to use a program on the same computer and user that compiled it.... – Alex Huszagh Jan 17 '18 at 00:56
  • @AlexanderHuszagh Aah. Thanks. Makes sense now. I will have to use `extern const std::string`. But, can I just define it this way: `extern const std::string FOLDER_PATH = get_home() + "/catkin_ws/src/abc/pqr/xyz"` – Mat_python Jan 17 '18 at 00:59
  • @Mat_python, You don't instantiate a variable using extern. But you can always do `const std::string FOLDER_PATH = get_home() + "/local/path/to/file"`, sure. Just make sure you don't mess with static initialization order fiasco. – Alex Huszagh Jan 17 '18 at 01:12