The script should be able to change argv[0] value in shell / bash script.
I found on an older post but couldn't really understand what it is doing.
Could someone please explain how the line works:
sh -c ". '$0'" argv0new "$@"
Also is test ".$INNERCALL"
meant to be a variable ?
Original question: How to change argv[0] value in shell / bash script?
#! /bin/bash # try executing this script with several arguments to see the effect
test ".$INNERCALL" = .YES || {
export INNERCALL=YES
# this method works both for shell and bash interpreters
sh -c ". '$0'" argv0new "$@"
# bash -c ". '$0'" argv0new "$@"
exit $?
}
printf "argv[0]=$0\n"
i=1 ; for arg in "$@" ; do printf "argv[$i]=$arg\n" ; i=`expr $i + 1` ;done
Let's go through this line by line
test ".$INNERCALL" = .YES
basically this sees if $INNERCALL
already has the value YES
in it. Shell's ""
function does variable expansion in a safe way, and marshalls it all into a value, e.g.
foo="hello"
bar=", world!"
echo "foobar is: $foo$bar"
prints foobar is: hello, world!
|| {
...
}
This uses the ||
operator to say, if the previous program returned with a non-zero value (e.g. test is a program that returns 1 if the associated conditional is false), then execute this code block, otherwise skip it (see this link for more on ||
)
export INNERCALL=YES
This sets INNERCALL
to YES
, which means that this only executes for the first level
sh -c ". '$0'" argv0new "$@"
exit $?
This is where the magic happens. sh -c
opens a new shell, which then reads its arguments from the string that follows. ". '$0'"
uses sh
's source function on the value currently at $0
, which is supposed to be this current file.
Basically sh -c ". '$0'"
just opens the current file again in a child sh
process, then the rest of the line replaces the arguments:
argv0new
becomes the new $0, and you keep the original arguments to the file by also including "$@"
then exit $?
returns whatever return value the child process runs.
the rest of the code is just to prove that all of the arguments except for $0
is the same, and $0
has been replaced.
tl;dr it opens up a child shell process, tells it to read the current file, replaces the arguments, and then exports a test value so that it doesn't infinite loop.
The script calls itself with a particular set of parameters when the variable INNERCALL
is unset. It sets the variable to avoid an infinite loop, then calls itself in a way which allows the script to set its own $0
. The inner instance then executes the code outside of the test
, which demonstrates that $0
is now indeed set to a particular value which the script's author chose. When this finishes, we return to the outer instance of the script, which then simply exits.
The real beef is that sh -c 'script...' arg0 arg1 arg2
sets $0
to the first argument after the script itself (arg0
in this example).
note it doesn't change the value of argv0 but create a new process with argv0new
changing
sh -c ". '$0'" argv0new "$@"
by
exec sh -c ". '$0'" argv0new "$@"
would also work changing bash by sh.
For bash From bash Invoking-Bash
-c
Read and execute commands from the first non-option argument command_string, then exit. If there are arguments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.