Skip to content

Commit

Permalink
Issue 15276: Allow users to specify shell for executeShell, pipeShell…
Browse files Browse the repository at this point in the history
…, spawnShell

This diff: This diff makes it possible to specify your preferred shell for the various `*shell` functions in `std.process`. By default it uses the new `nativeShell` function. `nativeShell` to always returns the native system shell (see the "some history" section for why this is the default value). I chose to create `nativeShell` rather than changeing `userShell` because I think it is still valuable to have something like `userShell` around in Phobos.

The one part of this diff I'm not super thrilled by is the use of variadic arguments (easier to see than to explain); if you have a suggestion for something better I'd love to hear it :).

Note: The default shell is a breaking change for Windows, which probably *should have* been broken same as posix in the diffs listed in the history section below, but I suspect was overlooked. Find rationale behind making a breaking change at all here: https://issues.dlang.org/show_bug.cgi?id=15000.

Some history:
a524a35
and
f537cb5

Tried to move us from a world where we preferred the shell specified in the SHELL environment variable to a world where we always went with the native system shell, /bin/sh for posix. The former of those revisions was reverted in:
5b2b1fb
Because the documentation was not updated to reflect the change.

Presently: we are in a state of schism, whereby `userShell` has behavior that does not align with what is actually used in `executeShell`, `pipeShell`, and `spawnShell`. This is something we *must* resolve.

Personal pain: This bit me when one of my scripts at work stopped working because it dependend on features that don't exist in `sh` (but do in `bash`, `zsh`, etc). It was really frustrating that there was no simple way to work around this and specify my preferred shell; it is, of course, possible to leverage the various `escape*` functions from `std.process` and `spawnProcess` to use whichever shell I prefer, but this seems like something warranting first class support. I was sad to not have something like this after a breaking change.
  • Loading branch information
Mark Isaacson authored and markisaa committed Jan 6, 2016
1 parent cda90ee commit 963f7e4
Showing 1 changed file with 73 additions and 39 deletions.
112 changes: 73 additions & 39 deletions std/process.d
Expand Up @@ -936,8 +936,7 @@ the current user's preferred _command interpreter (aka. shell).
The string $(D command) is passed verbatim to the shell, and is therefore
subject to its rules about _command structure, argument/filename quoting
and escaping of special characters.
The path to the shell executable is always $(CODE /bin/sh) on POSIX, and
determined by the $(LREF userShell) function on Windows.
The path to the shell executable defaults to $(LREF nativeShell).
In all other respects this function works just like $(D spawnProcess).
Please refer to the $(LREF spawnProcess) documentation for descriptions
Expand All @@ -960,7 +959,8 @@ Pid spawnShell(in char[] command,
File stderr = std.stdio.stderr,
const string[string] env = null,
Config config = Config.none,
in char[] workDir = null)
in char[] workDir = null,
string shellPath = nativeShell)
@trusted // TODO: Should be @safe
{
version (Windows)
Expand All @@ -969,13 +969,13 @@ Pid spawnShell(in char[] command,
// It does not use CommandLineToArgvW.
// Instead, it treats the first and last quote specially.
// See CMD.EXE /? for details.
auto args = escapeShellFileName(userShell)
auto args = escapeShellFileName(shellPath)
~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`;
}
else version (Posix)
{
const(char)[][3] args;
args[0] = "/bin/sh";
args[0] = shellPath;
args[1] = shellSwitch;
args[2] = command;
}
Expand All @@ -986,7 +986,8 @@ Pid spawnShell(in char[] command,
Pid spawnShell(in char[] command,
const string[string] env,
Config config = Config.none,
in char[] workDir = null)
in char[] workDir = null,
string shellPath = nativeShell)
@trusted // TODO: Should be @safe
{
return spawnShell(command,
Expand All @@ -995,7 +996,8 @@ Pid spawnShell(in char[] command,
std.stdio.stderr,
env,
config,
workDir);
workDir,
shellPath);
}

unittest
Expand Down Expand Up @@ -1642,24 +1644,26 @@ parameters are forwarded straight to the underlying spawn functions,
and we refer to their documentation for details.
Params:
args = An array which contains the program name as the zeroth element
and any command-line arguments in the following elements.
(See $(LREF spawnProcess) for details.)
program = The program name, $(I without) command-line arguments.
(See $(LREF spawnProcess) for details.)
command = A shell command which is passed verbatim to the command
interpreter. (See $(LREF spawnShell) for details.)
redirect = Flags that determine which streams are redirected, and
how. See $(LREF Redirect) for an overview of available
flags.
env = Additional environment variables for the child process.
(See $(LREF spawnProcess) for details.)
config = Flags that control process creation. See $(LREF Config)
for an overview of available flags, and note that the
$(D retainStd...) flags have no effect in this function.
workDir = The working directory for the new process.
By default the child process inherits the parent's working
directory.
args = An array which contains the program name as the zeroth element
and any command-line arguments in the following elements.
(See $(LREF spawnProcess) for details.)
program = The program name, $(I without) command-line arguments.
(See $(LREF spawnProcess) for details.)
command = A shell command which is passed verbatim to the command
interpreter. (See $(LREF spawnShell) for details.)
redirect = Flags that determine which streams are redirected, and
how. See $(LREF Redirect) for an overview of available
flags.
env = Additional environment variables for the child process.
(See $(LREF spawnProcess) for details.)
config = Flags that control process creation. See $(LREF Config)
for an overview of available flags, and note that the
$(D retainStd...) flags have no effect in this function.
workDir = The working directory for the new process.
By default the child process inherits the parent's working
directory.
shellPath = The path to the shell to use to run the specified program.
By default this is $(LREF nativeShell).
Returns:
A $(LREF ProcessPipes) object which contains $(XREF stdio,File)
Expand Down Expand Up @@ -1734,19 +1738,26 @@ ProcessPipes pipeShell(in char[] command,
Redirect redirect = Redirect.all,
const string[string] env = null,
Config config = Config.none,
in char[] workDir = null)
in char[] workDir = null,
string shellPath = nativeShell)
@safe
{
return pipeProcessImpl!spawnShell(command, redirect, env, config, workDir);
return pipeProcessImpl!spawnShell(command,
redirect,
env,
config,
workDir,
shellPath);
}

// Implementation of the pipeProcess() family of functions.
private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd)
private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...)
(Cmd command,
Redirect redirectFlags,
const string[string] env = null,
Config config = Config.none,
in char[] workDir = null)
in char[] workDir = null,
ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init)
@trusted //TODO: @safe
{
File childStdin, childStdout, childStderr;
Expand Down Expand Up @@ -1813,7 +1824,7 @@ private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd)

config &= ~(Config.retainStdin | Config.retainStdout | Config.retainStderr);
pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr,
env, config, workDir);
env, config, workDir, extraArgs);
return pipes;
}

Expand Down Expand Up @@ -2034,6 +2045,9 @@ maxOutput = The maximum number of bytes of output that should be
workDir = The working directory for the new process.
By default the child process inherits the parent's working
directory.
shellPath = The path to the shell to use to run the specified program.
By default this is $(LREF nativeShell).
Returns:
An $(D std.typecons.Tuple!(int, "status", string, "output")).
Expand Down Expand Up @@ -2073,27 +2087,34 @@ auto executeShell(in char[] command,
const string[string] env = null,
Config config = Config.none,
size_t maxOutput = size_t.max,
in char[] workDir = null)
in char[] workDir = null,
string shellPath = nativeShell)
@trusted //TODO: @safe
{
return executeImpl!pipeShell(command, env, config, maxOutput, workDir);
return executeImpl!pipeShell(command,
env,
config,
maxOutput,
workDir,
shellPath);
}

// Does the actual work for execute() and executeShell().
private auto executeImpl(alias pipeFunc, Cmd)(
private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)(
Cmd commandLine,
const string[string] env = null,
Config config = Config.none,
size_t maxOutput = size_t.max,
in char[] workDir = null)
in char[] workDir = null,
ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init)
{
import std.string;
import std.typecons : Tuple;
import std.array : appender;
import std.algorithm : min;

auto p = pipeFunc(commandLine, Redirect.stdout | Redirect.stderrToStdout,
env, config, workDir);
env, config, workDir, extraArgs);

auto a = appender!(ubyte[])();
enum size_t defaultChunkSize = 4096;
Expand Down Expand Up @@ -2212,14 +2233,14 @@ class ProcessException : Exception


/**
Determines the path to the current user's default command interpreter.
Determines the path to the current user's preferred command interpreter.
On Windows, this function returns the contents of the COMSPEC environment
variable, if it exists. Otherwise, it returns the string $(D "cmd.exe").
variable, if it exists. Otherwise, it returns the result of $(LREF nativeShell).
On POSIX, $(D userShell) returns the contents of the SHELL environment
variable, if it exists and is non-empty. Otherwise, it returns
$(D "/bin/sh").
variable, if it exists and is non-empty. Otherwise, it returns the result of
$(LREF nativeShell).
*/
@property string userShell() @safe
{
Expand All @@ -2228,6 +2249,19 @@ $(D "/bin/sh").
else version (Posix) return environment.get("SHELL", "/bin/sh");
}

/**
The platform-specific native shell path.
On Windows, this function returns $(D "cmd.exe").
On POSIX, $(D nativeShell) returns $(D "/bin/sh").
*/
@property string nativeShell() @safe @nogc pure nothrow
{
version (Windows) return "cmd.exe";
else version (Android) return "/system/bin/sh";
else version (Posix) return "/bin/sh";
}

// A command-line switch that indicates to the shell that it should
// interpret the following argument as a command to be executed.
Expand Down

0 comments on commit 963f7e4

Please sign in to comment.