Publi

Buscar un archivo en el PATH en C

En principio si vamos a ejecutar un programa con popen() o exec*() no habría problema. Siempre que exec sea de la familia de los exec*p(), es decir execvp(), execlp(), execvpe(), ya que estos buscan en la variable de entorno PATH el ejecutable, aunque los demás exec no lo hacen y requieren el fichero y path completo.

Podemos utilizarla, por ejemplo para ahorrarle a exec el trabajo de tener que buscar en el PATH si vamos a hacer muchas llamadas a un ejecutable o para mostrar al usuario la ruta y el nombre de archivo de un programa que va a ejecutar. También, un poco más abajo encontramos un bonus, que nos permite buscar archivos tanto en el PATH del sistema como en uno que nosotros espeifiquemos, y que nos será útil por ejemplo para buscar ejecutables en /usr/sbin, /usr/local/sbin y otros en sistemas en los que para nuestro usuario, ese ejecutable no se encuentra (hay más *nix que colores…)

En esta primera versión, somos nosotros los que debemos reservar memoria en una cadena de caracteres para almacenar el resultado, y el éxito o no de la ejecución vendrá dado por el return de la función:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

int findInPath(char *result, char *executable)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }

  if (found)
    strcpy(result, tmpstr);

  free(tmpstr);

  return found;
}

int main ()
{
  char res[200];
  if (findInPath(res, "ls"))
  {
     printf ("Found at: %s\n", res);
  }
  else
    printf ("Not found!\n");

  return 0;
}

En esta segunda versión, directamente devolvemos un char* como resultado de la función, por lo que si éste vale NULL significará que el archivo no se ha encontrado en el PATH. Eso sí, cuando no utilicemos más el valor, es nuestra responsabilidad liberar la memoria.
También me gustaría indicar que se podría haber hecho una reserva de memoria más eficiente, ahora mismo reservamos un tamaño igual al tamaño del PATH + longitud del nombre de archivo + 2 (una / y el terminador), esto sería en el peor de los casos, en el que en el PATH sólo haya un directorio, aunque generalmente no será así, aunque preferí esto a estar reservando y liberando memoria cada vez que lea un directorio, y no creo que el PATH sea tan excesivamente largo como para que haya un gran desperdicio de memoria.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

char* findInPath2(char *executable)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  if (found)
    return tmpstr;

  return NULL;
}

int main ()
{
  char *res = findInPath2("kwrite");
  if (res != NULL)
    {
      printf ("Found at: %s\n", res);
      free(res);
    }
  else
    printf ("Not found!\n");

  return 0;
}

Como toque final, vamos a presentar un tercer ejemplo donde además de buscar en el PATH buscamos en rutas puestas a mano:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

char* findInPath3(char *executable, char *additional_path)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *_additional_path;
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  if (found)
    return tmpstr;

  if (additional_path == NULL)
    return NULL;

  _additional_path = malloc(strlen(additional_path)+1);
  strcpy(_additional_path, additional_path);
  directory = strtok_r(_additional_path, ":", &saveptr);
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  free(_additional_path);

  if (found)
    return tmpstr;

  return NULL;
}

int main (int argc, char *argv[])
{
  char *res = findInPath3("puertas.jpg", (char*) "/home/usuario:/home/usuario/Descargas/Imágenes" );
  if (res != NULL)
    {
      printf ("Found at: %s\n", res);
      free(res);
    }
  else
    printf ("Not found!\n");
 
  return 0;
}

En este ejemplo es curioso cómo para strtok_r() es necesario hacer una copia de la cadena additional_path, más que nada porque ésta es modificada en cada iteración y si presentamos una cadena literal en realidad estamos pansando un valor constante y por tanto no podrá ser modificado y devolverá una violación de segmento. De todas formas, con strtok() y derivados es importante siempre trabajar con una copia (no lo hacemos con la variable path, porque no tiene uso más allá del bucle con strtok(), podemos probar poner su valor en pantalla al final del bucle para ver a lo que me refiero.

Como último apunte decir que he utilizado strtok_r porque éste es reentrante y es más seguro utilizar éste en aplicaciones multi-hilo. Si queremos utilizar el strtok() de toda la vida, perfecto, pero strtok_r() es más seguro, ya que la información necesaria para las iteraciones las almacena en una variable que nosotros controlamos. Por ejemplo strok() es un desastre también en funciones recursivas.

Foto: VectorOpenStock (CC-by)

Actualizado: 18/01/2015 : Arreglado el código del tercer ejemplo (no se veía bien en la web)

También podría interesarte....

There are 5 comments left Ir a comentario

  1. Pingback: Cómo obtener información de configuración del sistema Linux o tu servidor en C y C++ (II. Sistema y CPU) – Poesía Binaria /

  2. Pingback: Cómo obtener información de configuración del sistema Linux o tu servidor en C y C++ (II. Sistema y CPU) /

  3. ameron Diet /
    Usando Google Chrome Google Chrome 107.0.0.0 en Windows Windows NT

    Thanks for taking the time to write up such an interesting and informative post; I really appreciate it. Thank you so much; I really value your support. drift boss

  4. lily jane /
    Usando Google Chrome Google Chrome 107.0.0.0 en Mac OS X Mac OS X 10.15.7

    Wonderful post. In my opinion, this is one of the best posts that have been published on this website. Your work is of such high quality and it is very stunning. I truly appreciate it.
    wordle game

  5. Mike Rooney /
    Usando Google Chrome Google Chrome 108.0.0.0 en Windows Windows NT

    I really like the way that you have expressed yourself. There is a lot to be admired from this post. You might want to click onYellowstone Merch

Leave a Reply