3

I'd like to find a way to find out if a program is installed using C gnu11 standard, for example, will the command 'Rscript' work if it is executed using the system function in C.

I'm not a programmer by training, so I'm not sure if I'm using the correct search terms.

I would like the C equivalent of

con@con-Inspiron-3521:~/Scripts$ which Rscript 
/usr/bin/Rscript

how can I do that in C?

4
  • 1
    Well you could always use popen to run which Rscript and capture the output. Commented Dec 19, 2016 at 20:36
  • I don't understand your question, do you want to find out what compiled the program? Or if the program is installed? Or are you just trying to run a bash command in C? Commented Dec 19, 2016 at 20:36
  • @SanchkeDellowar I want to see if I can execute "Rscript" within a C program. Commented Dec 19, 2016 at 20:38
  • To the best of my knowledge there's no portable C way to do this. You may want to look at whether there's a POSIX equivalent, since that would still be decently portable. Commented Dec 19, 2016 at 20:49

2 Answers 2

6

With a similar effort you can just parse the PATH variable and check if the given file exists and is executable:

#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

bool can_run_command(const char *cmd) {
    if(strchr(cmd, '/')) {
        // if cmd includes a slash, no path search must be performed,
        // go straight to checking if it's executable
        return access(cmd, X_OK)==0;
    }
    const char *path = getenv("PATH");
    if(!path) return false; // something is horribly wrong...
    // we are sure we won't need a buffer any longer
    char *buf = malloc(strlen(path)+strlen(cmd)+3);
    if(!buf) return false; // actually useless, see comment
    // loop as long as we have stuff to examine in path
    for(; *path; ++path) {
        // start from the beginning of the buffer
        char *p = buf;
        // copy in buf the current path element
        for(; *path && *path!=':'; ++path,++p) {
            *p = *path;
        }
        // empty path entries are treated like "."
        if(p==buf) *p++='.';
        // slash and command name
        if(p[-1]!='/') *p++='/';
        strcpy(p, cmd);
        // check if we can execute it
        if(access(buf, X_OK)==0) {
            free(buf);
            return true;
        }
        // quit at last cycle
        if(!*path) break;
    }
    // not found
    free(buf);
    return false;
}
Sign up to request clarification or add additional context in comments.

11 Comments

@iharob: strchr is for the weak who loop over strings twice - once in strchr, once in strcpy. ;-) Just joking, it just felt more natural to write like this.
Yes, not checking malloc()'s return value is also very bad.
@iharob: malloc cannot fail on Linux systems (unless you ask for more than physical RAM + paging file), and in general I have yet to see any reasonably sized application deal "correctly" (whatever that means) with an out of memory. If malloc returns NULL for small allocations like this there's nothing you can reasonably do to save yourself, just crash. But still, if you prefer, I'll just add a useless check for that.
@RadLexus: here I'm referring to the "precise" version of the question i.e. "will the command 'Rscript' work if it is executed using the system function in C.". Of course it may be installed elsewhere, but then there's really no way to know unless you have either specific details about the program/the package manager/a full dump of the directory tree. :-P
@con: there was an error in the final loop, now it should be correct
|
1

The which program probably does a search for the file in any of the directories specified in the PATH environment variable.

There are many ways to do this, this is one such ways

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stddef.h>

#include <sys/stat.h>

int
main(int argc, char *argv[])
{
    const char *head;
    const char *command;
    size_t length;

    if (argc < 2) {
        fprintf(stderr, "insufficient number of arguments\n");
        return -1;
    }

    command = argv[1];
    length = strlen(command);
    // Get the PATH environment variable
    head = getenv("PATH");
    if (head == NULL) {
        fprintf(stderr, "the PATH variable was not set, what?\n");
        return -1;
    }
    // Find the first separator
    while (*head != '\0') {
        struct stat st;
        ptrdiff_t dirlen;        
        const char *tail;
        char *path;
        // Check for the next ':' if it's not found
        // then get a pointer to the null terminator
        tail = strchr(head, ':');
        if (tail == NULL)
            tail = strchr(head, '\0');
        // Get the length of the string between the head
        // and the ':'
        dirlen = tail - head;
        // Allocate space for the new string
        path = malloc(length + dirlen + 2);
        if (path == NULL)
            return -1;
        // Copy the directory path into the newly
        // allocated space
        memcpy(path, head, dirlen);
        // Append the directory separator
        path[dirlen] = '/';
        // Copy the name of the command            
        memcpy(path + dirlen + 1, command, length);
        // `null' terminate please
        path[dirlen + length + 1] = '\0';
        // Check if the file exists and whether it's
        // executable
        if ((stat(path, &st) != -1) && (S_ISREG(st.st_mode) != 0)) {
            if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) {                
                fprintf(stdout, "found `%s' but it's not executable\n", path);
            } else {
                fprintf(stdout, "found at: %s\n", path);
            }                
        }
        // Don't forget to free!
        free(path);
        // Point to the next directory
        head = tail + 1;
    }
    return 0;
}

in general the same algorithm

  1. Get the PATH environment variable, which consists of a sequence of directory paths separated by a : character.
  2. Obtain each directory path in this sequence by iterating over it, this is where you can do it many ways.
  3. Check if,

    • The file exists.
    • Has execution permissions for the caller.

2 Comments

Doesn't S_IXUSR just signify that the owner of the file can execute it?
@Rhymoid Yes it does, it would be better to use S_IXUSR | S_IXGRP | S_IXOTH

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.