Re: '/bin/sh -c' with multiple args
Taylor R Campbell <[email protected]> Fri, 5 Dec 2025 00:15:43 +0000
| Newsgroups | gmane.os.netbsd.devel.userlevel |
|---|---|
| Message-ID | <[email protected]> |
> Date: Thu, 4 Dec 2025 16:52:12 -0500 > From: Jan Schaumann <[email protected]> > > 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)