A lot of features of UNIX shells are build around pipe and dup and the fork + exec model. One can certainly implement in differently, but it is - like UNIX in general - very nice and elegant.
I don't think it is hack. I think it is a nice and clean API and the hate is largely irrational. I think one could improve usability for multi-threaded programs though.
Help me out here, please. Off the top of my head, the exec command is dependent on exec, except that a spawn + wait implementation would be a mostly okay substitute.
Pipes and redirections don’t need fork + exec. Neither do subshells.
If you use pipe() you get two ends in the same process, then you fork and child and parent can communicate. This is how a unix shell setups up pipes and it is rather elegant.
Doing the same thing on Windows, I create the pipe and get two ends in the same process. Then I'd call CreateProcess and indicate I want the pipe's handle (fd) inherited to the child, and I'd use a prearranged way to tell the child what the fd value is it should use.
Possibly the most common way to tell the child the value is by setting it as a CLI arg in CreateProcess.
Which special facilities are you referring to? If it's the ability to selectively inherit handles (fds) to a new process, Linux's lack of this "special facility" is nothing to be proud of.
How do you selectively pass on fds without having a global impact on your process?
As explained above, you fork and then before exec you can use all the usual APIs to close/duplicate/... file descriptors. This has no global impact on the parent process because you are already in the child, but one still has access to all the context of the parent needed to whatever configuration you want to do, including things that will be invented in 20 years and are not even conceived today.