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 6 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. the baby in yellow /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    I appreciate you providing this insightful information; I believe it will be beneficial to everyone.

  4. Mike Rooney /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    Good step you have written of writing content relating to cooperate sense. Better yet good working skills and hope you write more of this soon.
    Spider Verse Leather Vest

  5. Andrew Mark /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    This is an excellent post I seen thanks to share it. It is really what I wanted to see hope in future you will continue for sharing such a excellent post.
    Yellow Jacket Freddie Mercury

  6. Thomas mark /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    No problems when running a program through popen() or exec*() functions. As per criminal law act, exec functions from exec*p() family (like execvp(), execlp(), execvpe()) will search executable file in PATH. Other exec functions need complete path and filename.

Leave a Reply