user1011471
user1011471

Reputation: 1130

Best way in C to test whether `foo` is executable at the command line?

In C, what is the best way to find out if foo is available at the command line to execute on the host? If I were at the bash command line, I'd run type foo. In C, I could do type foo as a system call and check the exit status. But it's a good idea to avoid system calls when possible, right? Is there a better way?

My program may have to do a system command that eventually would run foo inside a shell script. (It's someone else's world and they supposedly use foo.) But if it can tell at the start that foo would be unavailable, it can avoid doing a lot of unnecessary computation because no foo means failure is certain.

Upvotes: 2

Views: 93

Answers (1)

antiduh
antiduh

Reputation: 12425

type (and its cousin, which) are commands provided by most Unix-like OS's and also implemented by most shells as an intrinsic.

You could simply invoke those commands by starting a child process from your program, and then read their output. If you want to rely on the behavior of a shell's version of the command, then you must start a child process that launches that shell, then command it to run type or which. If you'd rather not use child processes, then you must re-implement their logic in your program.

Using the FreeBSD implementation of which as a guide, we can see the basic steps for doing this:

  • Read the value of the $PATH environment variable.
  • Split apart $PATH into the various directories that it contains.
  • Test to see if the target file/program exists at one of the various sub directories in path.

And in code:

Read $PATH from the env:

if ((p = getenv("PATH")) == NULL)
    exit(EXIT_FAILURE);

Call print_matches with one of the arguments to which, providing the entire value of the $PATH:

while (argc > 0) {
    memcpy(path, p, pathlen);

    if (strlen(*argv) >= FILENAME_MAX ||
        print_matches(path, *argv) == -1)
        status = EXIT_FAILURE;
...

In print_matches, break apart the $PATH variable by splitting on the : character:

while ((d = strsep(&path, ":")) != NULL) {

And for each directory, concatenate the target program to the directory (checking to make sure the string doesn't become too big):

if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
        filename) >= (int)sizeof(candidate))
             continue;

And then test to see if that file exists:

if (is_there(candidate)) {

Upvotes: 2

Related Questions