execv vs execvp, why just one of them require the exact file’s path?

If the program name argument contains no slashes, the execvp() function looks for the program to execute in the directories listed on your PATH environment variable. If you don’t have . (the current directory) on your PATH and you aren’t in one of the directories listed on your path, a plain name like b will not be executed, even if b is in the current directory. If the name contains a slash, it can be relative (./b) or absolute (/home/someone/src/programs/b) and it will be interpreted as a file name to be executed without consulting the PATH environment variable.

By contrast, execv() treats a plain b in the program name argument as ./b — the name of the file in the current directory and executes it if it is present, and fails if it is located somewhere else.


At one time, there was a comment that asked:

Are you saying if you have an executable b in . and you do execv("b", b_args), it will get executed?

On a normal Unix box, yes.

Code b.c:

#include <stdio.h>
int main(void)
{
    puts("Hello");
    return 0;
}

Code a.c:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char *argv[] = { "b", 0 };
    execv(argv[0], argv);
    fprintf(stderr, "failed to execute '%s'\n", argv[0]);
    return 1;
}

Running these:

$ (PATH=$(clnpath "$PATH" ".:$PWD"); echopath PATH; ./a)
/Users/jleffler/bin
/opt/informix/12.10.FC6/bin
/Users/jleffler/oss/bin
/Users/jleffler/oss/rcs/bin
/usr/local/mysql/bin
/opt/gcc/v7.3.0/bin
/Users/jleffler/perl/v5.24.0/bin
/usr/local/bin
/usr/bin
/bin
/opt/gnu/bin
/usr/sbin
/sbin
Hello
$

The clnpath script modifies the string provided as its first argument ("$PATH") by removing any occurrences of any of the directory names listed in its second path-like argument (".:$PWD") — it’s how I edit my PATH on the fly when I need to. The echopath script echoes the directories on PATH (or any other path-like variable, or it will process the result of expanding a pathlike variable, such as "$PATH"), one per line — the output shows that neither . nor /Users/jleffler/soq (which is where I run the program) is on $PATH in the sub-shell. The ./a runs the code from a.c (it would not be executed without that ./ in front), which in turn runs the code from b.c, which produces the Hello. (If there is some system where this does not work, please identify it.)

I could also arrange for b.c to be:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    puts("Hello");
    const char *env = "PATH";
    char *val = getenv(env);
    if (val == 0)
        val = "<nothing>";
    printf("%s=%s\n", env, val);
    return 0;
}

which would print the value of $PATH directly from the executable (to verify that neither . nor the value of the current working directory is listed).

Leave a Comment