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