tech-userlevel archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: '/bin/sh -c' with multiple args
> Date: Thu, 4 Dec 2025 16:52:12 -0500
> From: Jan Schaumann <jschauma%netmeister.org@localhost>
>
> How does '/bin/sh -c' with multiple args actually
> work?
>
> The manual page says
>
> sh -c command_string [command_name [argument ...]]
>
> is valid, and should behave as such:
>
> "Read commands from the command_string operand instead
> of, or in addition to, from the standard input.
> Special parameter 0 will be set from the command_name
> operand if given, and the positional parameters (1, 2,
> etc.) set from the remaining argument operands, if
> any."
The important point is that command_string is interpreted as a shell
script, not an arbitrary path to a program to execute.
So try this:
$ sh -c 'echo 0=$0 1=$1 2=$2' foo bar baz quux
Exercise: Without running it, what is it going to print?
> So I would expect
>
> sh -c /bin/sleep whatever 30
>
> to lead to "/bin/sleep 30" being invoked with an argv0
> set to "whatever".
>
> However:
>
> $ sh -c /bin/sleep whatever 30
> usage: sleep seconds
ktrace it!
$ ktrace -t ac /bin/sh -c sleep whatever 30
usage: sleep seconds
$ kdump -t ac | grep -e ARG -e 'CALL.*execve'
1308 1308 ktrace CALL execve(0x7f7ffff10d7c,0x7f7ffff10750,0x7f7ffff10780)
1308 1308 ktrace ARG "/bin/sh"
1308 1308 ktrace ARG "-c"
1308 1308 ktrace ARG "sleep"
1308 1308 ktrace ARG "whatever"
1308 1308 ktrace ARG "30"
1308 1308 sh CALL execve(0x7e2e838,0x7e2e730,0x7e2e740)
1308 1308 sh ARG "sleep"
$
(The ARG lines represent argv[0], argv[1], &c.)
> Now I know that I can do
>
> $ sh -c "/bin/sleep 30"
>
> but the manual page suggests I should be able to set
> argv0 of the command as described above, but I don't
> see a way of actually doing that.
I don't think sh has a way to do that. It only has a way to set $0 in
a shell script you run.
There is only one call to execve in /bin/sh (under two #if branches):
226#ifdef SYSV
227 do {
228 execve(cmd, argv, envp);
229 } while (errno == EINTR);
230#else
231 execve(cmd, argv, envp);
232#endif
https://nxr.netbsd.org/xref/src/bin/sh/exec.c?r=1.59#226
The surrounding function tryexec has two callers:
1. When you write a path with a slash as a command name:
133 if (strchr(argv[0], '/') != NULL) {
134 tryexec(argv[0], argv, envp, vforked);
https://nxr.netbsd.org/xref/src/bin/sh/exec.c?r=1.59#133
2. When you don't write any slash in the command name:
140 while ((cmdname = padvance(&path, argv[0], 1)) != NULL) {
141 if (--idx < 0 && pathopt == NULL) {
142 /*
143 * tryexec() does not return if it works.
144 */
145 tryexec(cmdname, argv, envp, vforked);
https://nxr.netbsd.org/xref/src/bin/sh/exec.c?r=1.59#140
Both of these paths pass the command name as written in the shell
script verbatim, so it has to be the same path by which the shell
script found the command.
(There's also a call to execl, but it is only for /bin/pwd:
https://nxr.netbsd.org/xref/src/bin/sh/cd.c?r=1.56#522)
Home |
Main Index |
Thread Index |
Old Index