Sysop: | Amessyroom |
---|---|
Location: | Fayetteville, NC |
Users: | 23 |
Nodes: | 6 (0 / 6) |
Uptime: | 40:47:51 |
Calls: | 583 |
Calls today: | 1 |
Files: | 1,138 |
Messages: | 110,394 |
So, can anyone advise me; does the sh(1) "-c" option cause shell to
a) parse the given string into a commandline argument list, and then
b) exec(2) that argument list, replacing sh(1) with the specified
binary within the process?
Long ago, I played with Minix (v1.5), and wrote a SysV-style
init(8) for it. At that time, I couldn't see a proper way to
parse the "process" field of /etc/inittab, and simply crafted a
naive parser from strtok() calls and special handling for
redirect characters. I knew that this wasn't the way to do it; I
really needed to use the commandline parsing logic of sh(1).
Fast forward to now, and I'm looking at recycling my old Minix
init(8) for use in a Linux "homebrew" container environment, and
I want to fix this commandline parsing clumsiness. My current
thinking is that there may be some way to invoke sh(1) to
perform the parsing and execution for me. My only oddball
requirement is that sh(1) "get out of the way" (so to speak),
and directly /exec()/ the resulting parsed commandline, rather
than fork() and exec().
I noticed the "-c" sh(1) commandline option, and have puzzled
about it. From tests, it /appears/ to do what I want: parse the
given string into argv[] arguments, then exec() that list, but
the documentation doesn't really say that this behaviour is to
be expected.
From the OpenGroup's description of sh(1)[1], it /looks/ like
sh(1) is supposed to fork() and exec(), and the Linux sh(1)
manpage[2] is even less helpful about the behaviour of the -c
option. However, limited tests on my Linux boxen show that
bash(1) (at least version 4.3.48) appears to exec() only.
So, can anyone advise me; does the sh(1) "-c" option cause shell to
a) parse the given string into a commandline argument list, and then
b) exec(2) that argument list, replacing sh(1) with the specified
binary within the process?
[1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.htmlBy the way:-a There is new version of the POSIX standard:
Some sh implementations may exec() a final command as an optimization. Comparing sh -c '/bin/sleep 100', I see
FreeBSD sh (Almquist derivate): fork + exec
OpenBSD sh (PD ksh derivate): fork + exec
GNU bash: exec
I noticed the "-c" sh(1) commandline option, and have puzzled about it.
From tests, it /appears/ to do what I want: parse the given string into argv[] arguments, then exec() that list, but the documentation doesn't
really say that this behaviour is to be expected.
From the OpenGroup's description of sh(1)[1], it /looks/ like sh(1) is supposed to fork() and exec(),
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
So, can anyone advise me; does the sh(1) "-c" option cause shell to
a) parse the given string into a commandline argument list, and then
b) exec(2) that argument list, replacing sh(1) with the specified
binary within the process?
Not necessarily, because you can specify an arbitrary sequence of
commands with -c -- e.g. sh -c "date; uname -a" -- including more
complex things like loops and function definitions. Or you might give it
a command that only uses builtins, so it doesn't need to run any
external binaries.
However, it's a reasonable optimisation to exec the command when
possible. The latest versions of bash, mksh, busybox sh, dash and ksh93
all do this when given a single command with -c, and all except mksh
will exec the last command when given a sequence of simple commands.
Looking at the source for sysvinit-3.14's implementation of init, it
looks at the command given in inittab to see whether it contains any
quotes or shell metacharacters. If none are present, it splits up the
command on whitespace and calls execvp directly; if special characters
are found, it constructs a "sh -c" command and execvps that. busybox
init does exactly the same. So it sounds like your approach is
reasonable.
Neither sysvinit or busybox init prepends "exec" to the given command (although there's commented-out code in sysvinit suggesting that it did
do this at some point). This has the advantage that you can write
arbitrary complex shell commands in inittab, e.g. to set up the
environment for a process before starting it.
On 2025-08-29, Christian Weisgerber <naddy@mips.inka.de> wrote:
Some sh implementations may exec() a final command as an optimization.
Comparing sh -c '/bin/sleep 100', I see
FreeBSD sh (Almquist derivate): fork + exec
OpenBSD sh (PD ksh derivate): fork + exec
GNU bash: exec
Actually, I confused myself there. Allow me to revise:
FreeBSD sh (Almquist derivate): exec
OpenBSD sh (PD ksh derivate): fork + exec
GNU bash: exec
But notice that all three will switch to fork+exec if there is more
than a single command, e.g. sh -c '/bin/sleep 100; /bin/sleep 200'.
Not the "-c" option but the "exec" shell builtin command will do
what you want to be done:-a The "exec" shell builtin command tells
the shell not to fork a new process but rather exec the binary
replacing itself.
Long ago, I played with Minix (v1.5), and wrote a SysV-style init(8) for it.<snip>
I noticed the "-c" sh(1) commandline option, and have puzzled about it.
From tests, it /appears/ to do what I want: parse the given string into argv[] arguments, then exec() that list, but the documentation doesn't
really say that this behaviour is to be expected.
From the OpenGroup's description of sh(1)[1], it /looks/ like sh(1) is supposed to fork() and exec(), and the Linux sh(1) manpage[2] is even less helpful about the behaviour of the -c option. However, limited tests on my Linux boxen show that bash(1) (at least version 4.3.48) appears to exec() only.
So, can anyone advise me; does the sh(1) "-c" option cause shell to
a) parse the given string into a commandline argument list, and then
b) exec(2) that argument list, replacing sh(1) with the specified
binary within the process?
I noticed the "-c" sh(1) commandline option, and have puzzled about it.
From tests, it /appears/ to do what I want: parse the given string into argv[] arguments, then exec() that list, but the documentation doesn't
really say that this behaviour is to be expected.
Fast forward to now, and I'm looking at recycling my old Minix
init(8) for use in a Linux "homebrew" container environment, and I
want to fix this commandline parsing clumsiness. My current thinking
is that there may be some way to invoke sh(1) to perform the parsing
and execution for me. My only oddball requirement is that sh(1) "get
out of the way" (so to speak), and directly /exec()/ the resulting
parsed commandline, rather than fork() and exec().
Neither sysvinit or busybox init prepends "exec" to the given command (although there's commented-out code in sysvinit suggesting that it did
do this at some point). This has the advantage that you can write
arbitrary complex shell commands in inittab, e.g. to set up the
environment for a process before starting it.