tags:

views:

106

answers:

9

I have a program which opens a file by using a relative path (for instance '..').

Now the problem is, when I execute the program from another directory, the relative path is not relative to the program but relative to the working directory. Thus, if I start the program with '/path/to/program/myprog' it fails to find the file.

Is there a way to execute the program independently of the working directory? Id est, as If the working directory were the directory where the program is located? Or am I just thinking in a too complicated way and there is a much easier way to refer to a file, which location is only known by its path relative to the path of the program file?

+2  A: 

If program is not doing it by itself, it is a bad program. Bad programs should be wrapped with a bit of Bash scripting:

#!/bin/bash

set -e
cd $(readlink -f $(dirname $0))
exec ./myprog $*

The script above determines the directory where it is located, then changes current working directory to that directory and runs a program myprog from there, passing all parameters transparently. Thus, you have to put this script into the same directory where your program is located and run it instead of your program.

Assuming that you have the access to the source code and can fix the program, then use proc fs to determine the program's location and then use absolute path.

For example, /proc/self/exe will always be a symlink pointing at the binary file of the current process. Use readlink to read its value, then cut executable name and you got the directory.

Vlad Lazarenko
Isn't it kinda unconventional to override the user's choice of working directory like this (or programmatically using `chdir` as I mentioned)?
gspr
@gspr: Bash script does not override user's working directory. It overrides working directory for the executable that it runs. Because it needs it. User's directory is not getting changed.
Vlad Lazarenko
@Vlad: I meant the user's choice of working directory *for this program*. Isn't doing so sort of bad practice? I mean, as wilhelmtell, Let_Me_Be and I all point out, there are better ways to achieve the desired goal.
gspr
Yes, it is kind of a bad practice. But if program is dumb enough to use hard-coded relative path to access files and you cannot really fix the code, this is one of the ways to go. It also depends what program is doing etc. Other solution might be to run it from some other directory and just provide all files in the relative path it expects.
Vlad Lazarenko
You should probably pass in `"$@"` rather than `$*`
wilhelmtell
A: 

there has been a question a while ago how to find the location of the executable in C you could use this path to open your config, resource, etc ..

Nikolaus Gradwohl
A: 

Well, if your program needs to open a file from a location that depends on where the program is installed, you should probably make this a compile-time option. Have your build system set some CPP macro indicating the directory where the data files in question can be found. This is what the --datadir option to configure in a standard "configure, make, make install"-built program often does.

Of course, if you really want to, you can programmatically change the working dir with the chdir POSIX functions. But like I said, if a program needs to know where it is located, this should be provided at compile-time. Then you don't need to override the user's choice of working dir.

gspr
+1  A: 

One way is to use argv[0] - there is relative path of your program (for example ./programs/test/a.out). If you cut the program name and add the relative path to file, you will get a monster (for example ./programs/test/../../input_data) but it should work.

Dadam
This is not guaranteed, is it ?
ereOn
This is assuming he can modify the program. The question is about how to execute the program in its directory, not how to modify the program and fix this bug with relative path.
Vlad Lazarenko
How did I forget argv[0]? It can be inaccurate since it is set by the `execve` call, but it is usually correct and should be good enough to use for this.
nategoose
@Vlad Lazarenko: Since the OP tagged the question `c++` I guess we can assume that he has full control over the program.
ereOn
@ereOn: No. In Linux, proc file system should be used. I can do `exec` and pass "Clowns attack the world" as `argv[0]` and the whole thing will fail.
Vlad Lazarenko
@Vlad Lazarenko: Thanks, wasn't sure. Glad to see you don't like clowns either btw.
ereOn
@Vlad Lazarenko: At the extreme you can build Linux kernel without /proc
Dadam
@Dadam it is much harder to do than passing clown into argv[0]. Plus, many programs in Linux will just stop working.
Vlad Lazarenko
+1  A: 

The easiest way would be to either put your program in a pre-known place (/bin, /usr/bin, etc.). If not, you can use the argv[0], remove the program name (the last part), and use that as your working directory to prefix all relative paths (if you want relative paths to be relative to where your program is).

Also, you can determine the path of your program using the method above (use argv[0]), and then call a chdir() with this directory. All relative paths from then on would be relative to where the program is. Note, however, that in this case, you have to determine if argv[0] holds an absolute path. If not, you have to get the current working dir (getcwd()) and then append the directory part of argv[0]. Note, however, that changing the current work dir. is not a good idea, usually, as if a user gives you a file path as an argument, it will be relative to your current work dir, not relative to where the program is stored.

Some examples: Imagine your program lives in /usr/bin. You can call your program as:

/usr/bin/myprog

(that would be argv[0]. Prune the executable name and you have your dir.) Or, being, say, in /usr:

./bin/myprog

Now, argv[0] is a relative path. You have to prepend current working dir (/usr) to the one in the argv[0]: /usr/./bin/myprog, and then again prune the executable name. The directory would be again /usr/bin.

Diego Sevilla
+1  A: 

Don't use relative paths. Use absolute paths. You might have a constant defined in a config.h header file that specifies where your executable is installed. Then, prepend that string constant to any relative path you specify in your code.

wilhelmtell
A: 

openat opens a file relative to a particular directory file descriptor you pass it, but I don't think that is really what you want (exactly).

You will need to find the directory where the current executable is, and then create an open call relative to that (using either string operators to build the path, openat, or changing the current directory to that directory).

To find the executable you can readlink /proc/self/exe. readlink reads the path that a symbolic link points to, and /proc/self is a symbolic link to /proc/<PID> where <PID> is the process ID of the current process (handled special in the kernel), and the exe under that is a symbolic link to the executable file for that process. Then you'll need to fish out the path to that executable and use that.

All of that being said, you usually should avoid writing programs in such a way that they expect to find things relative to their executable file.

nategoose
A: 

You can determine the execution path from the argv[0] parameter, but be careful when doing so.

What you described is a well known and expected semantic. Users will expect this behaviour.

Let_Me_Be
A: 

Here is some code you can use to find your install-path from within your program (replace "test0002" with the name of your program):

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <unistd.h>

///=============================================================================
std::string FindInstallPath()
{
    std::string sret="";
    int pid = (int)getpid();
    bool b=false;
    std::string sf, s;
    std::stringstream ss;
    ss << "/proc/" << pid << "/maps";
    sf = ss.str();
    std::ifstream ifs(sf.c_str());
    size_t pos1, pos2;
    while (!b && ifs.good())
    {
        std::getline(ifs, s);
        if ((pos1 = s.rfind("test0002")) != std::string::npos)
        {
            if ((pos2 = s.find_first_of('/')) != std::string::npos)
            sret = s.substr(pos2, pos1 - pos2);
            b = true;
        }
    }
    if (!b) sret = "";
    ifs.close();
    return sret;
}
slashmais