6

I've tried the following short example to find out about a bug in a bigger program I am working on. It looks like QFile doesn't support unix (or the shell's) notation for the home directory:

#include <QFile>
#include <QDebug>

int main()
{
        QFile f("~/.vimrc");
        if (f.open(QIODevice::ReadOnly))
        {
                qDebug() << f.readAll();
                f.close();
        }
        else
        {
                qDebug() << f.error();
        }
}

As soon as I replace the "~" with my real home directory path, it works. Is there an easy workaround - some setting to enable? Or do I have to go the "ugly" way and ask QDir for the home directory of the current user and prepend that manually to each path?

Addendum: It's clear that usually the shell performs the tilde expansion so programs would never see that. Still it is so convenient in unix shells that I hoped the Qt implementation for file access would have that expansion included.

hurikhan77
  • 5,688
  • 3
  • 29
  • 46
  • 3
    It may be worthwhile to implement `operator/(QDir, std::string)` so you can write `QDir::homePath()/".vimrc"` – MSalters May 12 '10 at 11:29

4 Answers4

10

You can just create a helper function to do this for you, something like:

QString morphFile(QString s) {
    if ((s == "~") || (s.startsWith("~/"))) {
        s.replace (0, 1, QDir::homePath());
    }
    return s;
}
:
QFile vimRc(morphFile("~/.vimrc"));
QFile homeDir(morphFile("~"));

A more complete solution, allowing for home directories of other users as well, may be:

QString morphFile(QString fspec) {
    // Leave strings alone unless starting with tilde.

    if (! fspec.startsWith("~")) return fspec;

    // Special case for current user.

    if ((fspec == "~") || (fspec.startsWith("~/"))) {
        fspec.replace(0, 1, QDir::homePath());
        return fspec;
    }

    // General case for any user. Get user name and length of it.

    QString name (fspec);
    name.replace(0, 1, "");           // Remove leading '~'.
    int len = name.indexOf('/');      // Get name (up to first '/').
    len = (len == -1)
        ? name.length()
        : len - 1;
    name = name.left(idx);

    // Find that user in the password file, replace with home
    // directory if found, then return it. You can also add a
    // Windows-specific variant if needed.

    struct passwd *pwent = getpwnam(name.toAscii().constData());
    if (pwent != NULL)
        fspec.replace(0, len+1, pwent->pw_dir);

    return fspec;
}

Just one thing to keep in mind, the current solution is not portable to Windows (as per the comments in the code). I suspect this is okay for the immediate question since .vimrc indicates that's not the platform you're running on (it's _vimrc on Windows).

Tailoring the solution to that platform is possible, and indeed shows that the helper-function solution is a good fit since you'll only have to change one piece of code to add that.

paxdiablo
  • 772,407
  • 210
  • 1,477
  • 1,841
  • While I like the idea it will become cumbersome when handling cases like "~user" etc... – hurikhan77 May 12 '10 at 07:07
  • Your code probably shouldn't be attempting to fiddle around in another user's directory anyway :-) But, yes, if you want the full expansions, you'll probably need to go out to the shell to get them. I was only answering the specific case in your question, that of `~`. Still, if you need the entire gamut, it's _still_ best to isolate the code to this single helper function. If it's just `~/`, use what I've given. If it's `~`, find another way within the function, such as running a subshell to retrieve the value of it. I can't see a less cumbersome way of doing what you want. – paxdiablo May 12 '10 at 07:11
  • In other words, even if Qt provided this functionality, it would probably be doing it the way I'm suggesting (although possibly a little more cross-platform). – paxdiablo May 12 '10 at 07:16
  • Well, you could do a pwent lookup or something, I'm sure. As a quickfix I am going with QString("%1/.vimrc").arg(QDir::homePath()). I will investigate a more elegant solution later. Maybe someone posts it. ;-) – hurikhan77 May 12 '10 at 07:26
  • 1
    @paxdiablo: While ~user can be used to define the home of 'user', that is not as widely used as plain ~ to refer to your own home directory. Anyway you can detect the difference by looking for ~ as the first character and then checking whether the second character is / or not. Dealing with ~user would get a lot messier... but you can at least detect it and complain :) – David Rodríguez - dribeas May 12 '10 at 08:13
  • 1
    Okay, I've added some (untested) code which should come close to handling other users as well. Keep in mind it's not thread-safe, you ll need to use the `_r` versions if you want that. In fact, it may not even compile but it's a good start :-) Feel free to edit once you get it working (or let me know the problems and I'll fix them myself). Cheers. – paxdiablo May 12 '10 at 08:20
  • 1
    @paxdiablo: If the user puts `~foo/bar.baz` in a configuration file (or enters it in a dialog; I've had to deal with that in the past) then it's not the programmer's place to say "don't fiddle around in other people's home directories". Leave saying that to the OS, or maybe the other user will actually have granted permission on that file. This sort of thing happens when code hits the ground. – Donal Fellows May 12 '10 at 08:33
  • @Donal, I'm not sure if you noticed the smiley at the end of that sentence. But you raise a good point, which is hopefully now addressed in the updated answer. – paxdiablo May 12 '10 at 08:40
  • The question is not about if one should or should not poke around in other users directories. In this case I need it for sharing some printing templates between users with one user able to keep and edit in his home dir. However, this won't be a permanent solution, it will be replaced later. – hurikhan77 May 12 '10 at 08:55
3

It has nothing to do with not supporting UNIX; the expansion of tildes to the user's home directory is a substitution performed by the shell, so yes, you will have to manually replace them.

Michael Aaron Safyan
  • 87,518
  • 14
  • 130
  • 194
  • Yes, I know it's a shell expansion. But it is so convenient that I hoped that the unix Qt implementation would support and expand on the tilde. – hurikhan77 May 12 '10 at 06:48
2

Please submit a suggestion to the Qt bugtracker.

https://bugreports.qt.io/

MBach
  • 1,509
  • 14
  • 28
guruz
  • 1,554
  • 13
  • 21
0

Take a look at the C library function glob, which will do tilde expansion (and possibly wildcard expansion and various other functions too).

Andrew McGregor
  • 24,492
  • 2
  • 26
  • 28
  • I suppose that will involve excaping other special globbing chars then which I don't want to fiddle around with. And I have to handle multiple entries in the returned list somehow because glob will not return a single string (though a list containing probably only one string). – hurikhan77 May 12 '10 at 07:31
  • 1
    That depends on your OS; for example, OS X has a GLOB_LIMIT flag, that will let you ask for only one result. For what may be an even more useful function, try `wordexp`, which lets you do most of what the shell does in constructing commands. – Andrew McGregor May 12 '10 at 09:40