Forum Programmation.c C : gestion du répertoire de travail

Posté par  . Licence CC By‑SA.
Étiquettes :
0
15
mar.
2023

Bonjour à tous,

Je recherche le moyen le plus simple (au sens "moins de lignes de code") de récupérer le répertoire de travail d'un programme.

Supposons que le programme ait besoin d'ouvrir un fichier data.txt situé dans le même répertoire que l'exécutable. Le plus simple est d'écrire :

f=fopen("data.txt","r");

Mais si j'appelle le programme d'un autre emplacement que celui de l'exécutable, ça ne marche pas. Il faut donc récupérer le répertoire de travail.

Voici les deux méthodes les plus simples que j'ai trouvées :

Méthode 1 :

#define _GNU_SOURCE
#include <libgen.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
    {
    FILE *f;
    char *racine;
    char *chemin;
    char nom_fichier[]="/data.txt";

    racine=strdupa(argv[0]);
    chemin=malloc(strlen(dirname(racine))+strlen(nom_fichier)+1);
    strcpy(chemin,racine);
    strcat(chemin,nom_fichier);

    if ((f=fopen(chemin,"r")) != NULL)
        {
        ...

Si l'on veut se passer de l'extension GNU, on peut remplacer racine=strdupa(argv[0]); par :

racine=malloc(strlen(argv[0])+1);
strcpy(racine,argv[0]);

Bien sûr ceci a peu d'intérêt étant donné le caractère anecdotique des systèmes d'exploitations autres que GNU-Linux.

Méthode 2 :

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
    {
    FILE *f;
    int l;
    char *chemin;
    char nom_fichier[]="data.txt";

    l=strlen(argv[0])-strlen(program_invocation_short_name);
    chemin=malloc(l+strlen(nom_fichier)+1);
    memcpy(chemin,argv[0],l);
    strcat(chemin,nom_fichier);

    if ((f=fopen(chemin,"r")) != NULL)
        {
        ...

On peut simplifier encore plus en se passant des malloc en assignant une dimension fixe aux chaînes, mais c'est moins propre.

Et vous comment faites vous ?

  • # getcwd ?

    Posté par  . Évalué à 3.

    • [^] # Re: getcwd ?

      Posté par  (site web personnel) . Évalué à 3.

      Supposons que le programme ait besoin d'ouvrir un fichier data.txt situé dans le même répertoire que l'exécutable. Le plus simple est d'écrire :

      Là où est l'exécutable et le répertoire courant sont deux choses différentes.

      git is great because linus did it, mercurial is better because he didn't

  • # Répertoire du fichier exécutable

    Posté par  (site web personnel) . Évalué à 8.

    Mais si j'appelle le programme d'un autre emplacement que celui de l'exécutable, ça ne marche pas. Il faut donc récupérer le répertoire de travail.

    Objection. Tu veux récupérer le répertoire du fichier exécutable. Le répertoire de travail, que tu n'as pas modifié, c'est celui depuis lequel tu as lancé le programme.

    • [^] # Re: Répertoire du fichier exécutable

      Posté par  . Évalué à 1.

      En tenant compte de cette remarque je proposerai

      #include <stdio.h> // for printf()
      #include <stdlib.h> // for realpath()
      #include <libgen.h> // for dirname()
      
      int main(int argc, char **argv)
      {
          char *argv0path = realpath(argv[0], NULL);
          char *argv0dir = dirname(argv0path);
      
          printf("Name of the dir containing my executable:%s\n", argv0dir);
          free(argv0path);
      }
    • [^] # Re: Répertoire du fichier exécutable

      Posté par  . Évalué à 2.

      Oui tu as raison. Après vérification, il y a bien une confusion terminologique de ma part.

      Je croyais que le répertoire de travail était celui où le programme travaille. C'est à dire celui où se trouve l'exécutable et les fichiers annexes. En fait c'est bien le répertoire d'où il est lancé.

  • # Bonne pratique ?

    Posté par  (Mastodon) . Évalué à 4. Dernière modification le 15 mars 2023 à 14:11.

    Je ne suis pas certain que ce soit une bonne pratique. En tous cas je te conseille de garder ce comportement pour une utilisation très précise et parfaitement maîtrisée (par exemple un système embarqué dont tu es le concepteur).

    En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: Bonne pratique ?

      Posté par  . Évalué à 1.

      Qu'est ce qui n'est pas une bonne pratique ?

      • [^] # Re: Bonne pratique ?

        Posté par  (Mastodon) . Évalué à 7. Dernière modification le 15 mars 2023 à 19:12.

        D'écrire un fichier à l'emplacement d'où est l'exécutable : en général les fichiers temporaires vont dans /tmp, le fichiers de config dans $HOME etc.

        Peut-être que l'exécutable est dans un répertoire où l'utilisateur n'a pas les droits (cas fréquent sur un système classique), ou l'impossibilité de lancer l'exécutable depuis un répertoire en lecture seule etc.

        En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

  • # C'est le genre de remarque qui a tendance à m'agacer

    Posté par  . Évalué à 0. Dernière modification le 15 mars 2023 à 14:42.

    Bien sûr ceci a peu d'intérêt étant donné le caractère anecdotique des systèmes d'exploitations autres que GNU-Linux.

    Dans ce cas inutile de coder pour du GNU Linux étant donné le caractère anecdotique des systèmes autre que windows.

    Après je ne dis pas qu'il faille tout rendre portable sur tous les systèmes, mais si ça coute peu de faire quelque chose qui marche partout, pourquoi s'en priver ?

  • # Nope

    Posté par  . Évalué à 1.

    man 3p exec

    The value in argv[0] should point to a filename string that is associated with the process being started by one of the exec functions.

    man 3 exec

    The char *const argv[] argument is an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a null pointer.

    Bonus :

    ~ /bin/ls -l /proc/self/exe
    lrwxrwxrwx 1 woke users 0 Mar 16 12:07 /proc/self/exe -> /bin/ls

    man proc

    /proc/pid/exe Under Linux 2.2 and later, this file is a symbolic link containing the actual pathname of the executed command. This symbolic link can be dereferenced normally; attempting to open it will open the executable. You can even type /proc/pid/exe to run another copy of the same executable that is being run by process pid. If the pathname has been unlinked, the symbolic link will contain the string '(deleted)' appended to the original pathname. In a multithreaded process, the contents of this symbolic link are not available if the main thread has already terminated (typically by calling pthread_exit(3)).

    man hier

  • # Portable ou pas portable ?

    Posté par  (site web personnel) . Évalué à 2.

    En pas portable (Linux only) tu peux simplement lire /proc/self/exe. En très portable c'est plutôt compliqué mais il existe des modules sympathiques comme whereami

    git is great because linus did it, mercurial is better because he didn't

  • # Erreur possible avec malloc ?

    Posté par  . Évalué à 3.

    Il me semble qu'il y a un truc qui ne va pas dans mon code, ici :

    l=strlen(argv[0])-strlen(program_invocation_short_name);
    chemin=malloc(l+strlen(nom_fichier)+1);
    memcpy(chemin,argv[0],l);
    strcat(chemin,nom_fichier);

    Le memcpy ne rajoute pas de caractère nul à la fin donc si l'espace alloué par malloc n'est pas vide, chemin ne sera pas une chaîne et le strcat peut planter…

    • [^] # Re: Erreur possible avec malloc ?

      Posté par  (Mastodon) . Évalué à 3.

      Le memcpy ne rajoute pas de caractère nul à la fin

      C'est normal, pas d'inquiétude, memcpy, c'est fait pour copier de la mémoire et pas des chaines de caractères. Pour ça, il faut utiliser strcpy. Il faut regarder les deux manpages et bien capter la différence entre les deux.

      Le C, c'est réjouissant :)

      • [^] # Re: Erreur possible avec malloc ?

        Posté par  . Évalué à 2.

        Oui je connais bien la différence entre memcpy(chaîne1,chaîne2,longueur) et strcpy(chaîne1,chaîne2) ; mais dans ce cas on ne veut copier qu'une partie de argv[0], d'où l'usage de memcpy avec le paramètre "longueur".

        Donc dans le code, il faut en fait remplacer malloc par calloc, qui initialise "chemin" en le remplissant avec des caractères nuls.

  • # Version finale

    Posté par  . Évalué à 1.

    Voici la version finale, où j'espère ne pas avoir fait d'erreur dans les allocations mémoires. Elle a pour avantage d'être portable, de ne pas utiliser de bibliothèques exotiques, et de conserver la valeur du chemin de l'exécutable (variable racine) :

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
        {
        FILE *f;
        int l,r=0;
        char *chemin,*racine;
        char nom_fichier[]="data.txt";
    
        // Récupération du répertoire de l'exécutable
    
        l=strlen(argv[0])-strlen(strrchr(argv[0],'/')+1);
        racine=calloc(l+1,1);
        memcpy(racine,argv[0],l);
    
        // Récupération du chemin complet du fichier à ouvrir
    
        chemin=malloc(l+strlen(nom_fichier)+1);
        strcpy(chemin,racine);
        strcat(chemin,nom_fichier);
    
        if ((f=fopen(chemin,"r")) != NULL)
            {
            puts("OK");
            fclose(f);
            }
        else
            {
            puts("Erreur de fichier");
            r=1;
            }
        return r;   
        }
    • [^] # Re: Version finale

      Posté par  (Mastodon) . Évalué à 2. Dernière modification le 19 mars 2023 à 09:30.

      Et tu peux aussi rajouter un perror(nom_fichier); avant le dernier puts.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.