From 963f7e4170fd02aa07c95562f2554807a8fe9178 Mon Sep 17 00:00:00 2001 From: Mark Isaacson Date: Sun, 3 Jan 2016 21:46:19 -0800 Subject: [PATCH] Issue 15276: Allow users to specify shell for executeShell, pipeShell, 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: https://github.com/D-Programming-Language/phobos/commit/a524a3571b18e440c4dd751fcf4e2d00b834fb22 and https://github.com/D-Programming-Language/phobos/commit/f537cb50b5f8f9ad6d841db14b2e0a6cb845d008 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: https://github.com/D-Programming-Language/phobos/commit/5b2b1fb59450cfc32ecec3252f543a9f6f34c2a4 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. --- std/process.d | 112 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/std/process.d b/std/process.d index 8e11fa46c3a..7c43f1f43ba 100644 --- a/std/process.d +++ b/std/process.d @@ -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 @@ -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) @@ -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; } @@ -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, @@ -995,7 +996,8 @@ Pid spawnShell(in char[] command, std.stdio.stderr, env, config, - workDir); + workDir, + shellPath); } unittest @@ -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) @@ -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; @@ -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; } @@ -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")). @@ -2073,19 +2087,26 @@ 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; @@ -2093,7 +2114,7 @@ private auto executeImpl(alias pipeFunc, Cmd)( 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; @@ -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 { @@ -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.