4

I'm reading up on exec and how to use the -a flag. The SS64 docs seem accurate; they say the following:

exec:

Execute a command

Syntax:

exec [-cl] [-a name] [command [arguments]]

Options:

-c   Causes command to be executed with an empty environment.

-l   Place a dash at the beginning of the zeroth arg passed to command.
     (This is what the login program does.)

-a   The shell passes name as the zeroth argument to command.

To test this out, I wrote two scripts, one named foo/baz and one named foo/buzz:

#!/usr/bin/env bash
# foo/baz

exec -a blah ./foo/bar 1 2
#!/usr/bin/env bash
# foo/buzz

exec ./foo/bar 1 2

Each of these scripts runs the same child script, foo/bar, which does the following:

#!/usr/bin/env bash

echo "Hello world"
echo "0: $0"
echo "1: $1"
echo "2: $2"

My goal is to see what effect -a has on the 0th and subsequent arguments. If -a causes the 0th argument to change to the argument you pass to the -a flag, then I would expect the 0th argument when I run foo/baz to be blah, since that's what I pass to -a. However, when I run the scripts, the output is the same in both cases:

~/Workspace/OpenSource (master)  $ ./foo/baz 
Hello world
0: /Users/richiethomas/Workspace/OpenSource/foo/bar
1: 1
2: 2
~/Workspace/OpenSource (master)  $ ./foo/buzz
Hello world
0: /Users/richiethomas/Workspace/OpenSource/foo/bar
1: 1
2: 2

Am I doing something wrong? Or is my expectation incorrect somehow?

Also, a related question- what is the use case of overriding the 0th argument, as opposed to just accessing any passed-in args via $1, $2, $3, etc.?

5

3 Answers 3

13

what is the use case of overriding the 0th argument...?

Some programs change their behavior based on how they are called. For example, busybox is a multi-call binary that behaves this way. Using exec -a we get different behavior depending on the value of the zeroth argument:

$ bash -c 'exec -a date /usr/sbin/busybox'
Sat Sep 17 20:22:14 EDT 2022
$ bash -c 'exec -a uptime /usr/sbin/busybox'
 20:22:17 up 23:48,  load average: 0.10, 0.20, 0.15

This also demonstrates that exec -a <something> behaves as documented.

Am I doing something wrong? Or is my expectation incorrect somehow?

The problem here is that you're working with shell scripts. When you enter ./foo/baz on the command line, you're not actually running a command named ./foo/baz: you're running something like /bin/bash /path/to/foo/baz. While exec -a effects the zeroth argument passed to the shell...the shell doesn't care, and it uses its own logic when setting up the variables visible to the shell script, including $0 (which contains the script name), and the positional parameters $1, $2... (which contain the arguments to the script).

(This isn't specific to shell scripts -- the same would hold true of pretty any interpreted code.)

1
  • 1
    It should also be noted that, while some interpreters will try to preserve the value of the 0th argument, /usr/bin/env typically won't, so bash isn't even at fault here.
    – Kevin
    Sep 18, 2022 at 17:59
4

It's because you're calling an interpreted script rather than a native binary. Compile this C program, putting the result at ./foo/bar where your script used to be:

#include <stdio.h>

int main(int argc, char **argv) {
    puts("Hello world");
    for(int i = 0; i < argc; ++i)
        printf("%d: %s\n", i, argv[i]);
}

Once you do that, you'll see the behavior you were expecting. The reason for the discrepancy is that when calling interpreted scripts, argv[0] gets hijacked to tell the interpreter the name of the script. Usually this goes unnoticed, since in most cases, that's what it was anyway. You only noticed it because you went out of your way to make it different.

1
  • you also get $0 different from the original argv[0] with something like sh somescript.sh foo bar.
    – ilkkachu
    Sep 18, 2022 at 16:01
2

The key point is that exec will set the $0 only for the command on that command line.

Try, for example:

$ bash

$ exec -a SomeName sh -c 'echo "The name is: $0"'
The name is: SomeName

It just so happens that the shell closes because the exec replaces it and when that ends, the shell process also ends. That is why you need to ask for a new shell using the bash command. If not, the shell will close and will also close the terminal with it.

If you want to follow what the exec command does, try something like:

$ strace -fe execve bash -c 'exec -a SomeName echo test' one two
execve("/usr/bin/bash", ["bash", "-c", "exec -a SomeName echo test", "one", "two"], 0x7ffdca326e60 /* 77 vars */) = 0
execve("/usr/bin/echo", ["SomeName", "test"], 0x55d4dcf02c50 /* 76 vars */) = 0
test
+++ exited with 0 +++

And that is useful to make some executable changes as its calling name changes:

$ ( exec -a date -- busybox -ud '12:34' +'%F %T %Z' )
2022-09-18 12:34:00 UTC

$ ( exec -a ls busybox )
fileone
filetwo

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .