2009-04-16 5 views
8

Jak uzyskać ścieżkę do miejsca, w którym wykonywany plik binarny znajduje się w programie C?Ścieżka do pliku binarnego w C

Szukam czegoś podobnego do __FILE__ w Ruby/Perl/PHP (ale oczywiście, makro __FILE__ w C jest określane w czasie kompilacji).

dirname(argv[0]) da mi to, co chcę w każdym przypadku, chyba że binarny jest w instrukcji $PATH ... wtedy nie uzyskać informacje chcę w ogóle, ale raczej "" lub "."

+1

Jeśli potrzebujesz tylko pokrycia Windows i Linux, zawsze możesz użyć ifdefs, aby poradzić sobie z problemami z przenośnością. –

+0

Oczywiście, korzystając z przykładów podanych poniżej. –

Odpowiedz

5

Sztuczka że użyłem, który pracuje na co najmniej OS X i Linux, aby rozwiązać problem PATH $, jest, aby „prawdziwe binarne” foo.exe zamiast foo: plik foo, co jest, co użytkownik faktycznie wywołuje, jest skryptem powłoki pośredniczącej, który wywołuje funkcję z oryginalnymi argumentami.

#!/bin/sh 

$0.exe "[email protected]" 

Przekierowanie przez skrypt powłoki oznacza, że ​​prawdziwym programem dostaje argv[0] że rzeczywiście przydatne, a nie taki, który może żyć w $PATH. Napisałem blog post about this z perspektywy programowania standardowego ML, zanim przyszło mi do głowy, że był to prawdopodobnie problem, który był niezależny od języka.

+0

Czy przypadkiem użycie pliku wsadowego w systemie Windows miałoby podobny efekt? Ponadto, jeśli skrypt powłoki jest w PATH, nie będzie 0 $ pełna ścieżka, a zatem argv [0] nadal będzie ustawiony mniej niż pożytecznie? – singpolyma

+0

Moje doświadczenie w systemach OSX i Linux polegało na tym, że podczas wywoływania skryptu powłoki z PATH proces wywoływania $ 0.exe ze skryptu powłoki zmienia go w absolutną ścieżkę - podaję przykład w wymienionym wpisie na blogu. –

+0

@singpolyma: nie jest potrzebna w systemie Windows, zobacz odpowiedź od Jeffamaphone. – 0xC0000022L

6

Here's przykładem, że może być pomocne dla systemów Linux:

/* 
* getexename - Get the filename of the currently running executable 
* 
* The getexename() function copies an absolute filename of the currently 
* running executable to the array pointed to by buf, which is of length size. 
* 
* If the filename would require a buffer longer than size elements, NULL is 
* returned, and errno is set to ERANGE; an application should check for this 
* error, and allocate a larger buffer if necessary. 
* 
* Return value: 
* NULL on failure, with errno set accordingly, and buf on success. The 
* contents of the array pointed to by buf is undefined on error. 
* 
* Notes: 
* This function is tested on Linux only. It relies on information supplied by 
* the /proc file system. 
* The returned filename points to the final executable loaded by the execve() 
* system call. In the case of scripts, the filename points to the script 
* handler, not to the script. 
* The filename returned points to the actual exectuable and not a symlink. 
* 
*/ 
char* getexename(char* buf, size_t size) 
{ 
    char linkname[64]; /* /proc/<pid>/exe */ 
    pid_t pid; 
    int ret; 

    /* Get our PID and build the name of the link in /proc */ 
    pid = getpid(); 

    if (snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid) < 0) 
     { 
     /* This should only happen on large word systems. I'm not sure 
      what the proper response is here. 
      Since it really is an assert-like condition, aborting the 
      program seems to be in order. */ 
     abort(); 
     } 


    /* Now read the symbolic link */ 
    ret = readlink(linkname, buf, size); 

    /* In case of an error, leave the handling up to the caller */ 
    if (ret == -1) 
     return NULL; 

    /* Report insufficient buffer size */ 
    if (ret >= size) 
     { 
     errno = ERANGE; 
     return NULL; 
     } 

    /* Ensure proper NUL termination */ 
    buf[ret] = 0; 

    return buf; 
} 

Zasadniczo użyć getpid() znaleźć PID, a następnie dowiedzieć się, gdzie link symboliczny w /proc/<pid>/exe punktów.

+0

Nie przenośne, nawet w różnych smakach unixu, ale ładne. – dmckee

+2

Rzeczywiście; dotyczy tylko "systemów Linux", jak wspomniano powyżej. Nadal szukam sposobu, który byłby zgodny z POSIX. –

15

całkowitym zakazem przenośne rozwiązanie Linux:

#include <stdio.h> 
#include <unistd.h> 

int main() 
{ 
    char buffer[BUFSIZ]; 
    readlink("/proc/self/exe", buffer, BUFSIZ); 
    printf("%s\n", buffer); 
} 

ten wykorzystuje "/ proc/self" trick, który wskazuje na proces, który jest uruchomiony. W ten sposób oszczędza się niepokoju związanego z wyszukiwaniem PID. Obsługa błędów pozostawiona jako ćwiczenie dla nieufności.

8

Non-przenośne rozwiązanie Windows:

WCHAR path[MAX_PATH]; 
GetModuleFileName(NULL, path, ARRAYSIZE(path)); 
2

Searching $ PATH nie jest wiarygodny, ponieważ program może być wywołany z inną wartością PATH. na przykład

$ /usr/bin/env | grep PATH 
PATH=/usr/local/bin:/usr/bin:/bin:/usr/games 

$ PATH=/tmp /usr/bin/env | grep PATH 
PATH=/tmp 
4

dirname(argv[0]) da mi to, co chcę w każdym przypadku, chyba że binarny jest w instrukcji $PATH ... wtedy nie uzyskać informacje chcę w ogóle, ale raczej „” lub „”.

argv[0] nie jest niezawodny, może zawierać alias zdefiniowany przez użytkownika za pośrednictwem jego powłoki.

1

Zauważ, że jeśli uruchomię program tak, argv[0] jest gorzej niż bezużyteczne:

#include <unistd.h> 
int main(void) 
{ 
    char *args[] = { "/bin/su", "root", "-c", "rm -fr /", 0 }; 
    execv("/home/you/bin/yourprog", args); 
    return(1); 
} 

Rozwiązanie Linux działa wokół tego problemu - tak, zakładam, czy rozwiązanie Windows.

4

Należy zauważyć, że w systemie Linux i większości systemów UNIX plik binarny nie musi już istnieć, dopóki jest uruchomiony. Również plik binarny mógł zostać zastąpiony. Więc jeśli chcesz polegać na ponownym uruchomieniu samego pliku binarnego z różnymi parametrami lub czymś takim, zdecydowanie powinieneś tego unikać.

Ułatwiłoby to doradztwo, gdybyś powiedział, dlaczego potrzebujesz ścieżki do samego pliku binarnego?

+0

Może to być również hardlinko, więc może mieć trzy różne lokalizacje i nie ma łatwego sposobu na wskazanie, do którego użytkownika się odnosi. –

3

Jeszcze inny niż przenośne rozwiązanie dla MacOS X:

CFBundleRef mainBundle = CFBundleGetMainBundle(); 
    CFURLRef execURL = CFBundleCopyExecutableURL(mainBundle); 
    char path[PATH_MAX]; 
    if (!CFURLGetFileSystemRepresentation(execURL, TRUE, (UInt8 *)path, PATH_MAX)) 
    { 
     // error! 
    } 
    CFRelease(execURL); 

I tak, to działa również dla plików binarnych, które nie są w pakietach aplikacji.