Skip to content

Commit 083cb4a

Browse files
GregPlowmanmusm
authored andcommitted
REPL shell mode for Windows
REPL shell mode for Windows Documentation JULIA_SHELL environment variable Interacting with Julia docs - xref JULIA_SHELL Fix whitespace JULIA_SHELL doc Fix JULIA_SHELL cross-reference Address code review comments rebase fix
1 parent 2b5faef commit 083cb4a

File tree

5 files changed

+76
-9
lines changed

5 files changed

+76
-9
lines changed

base/Base.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ include("iobuffer.jl")
174174
include("intfuncs.jl")
175175
include("strings/strings.jl")
176176
include("parse.jl")
177-
include("shell.jl")
178177
include("regex.jl")
178+
include("shell.jl")
179179
include("show.jl")
180180
include("arrayshow.jl")
181181
include("methodshow.jl")

base/client.jl

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,15 @@ stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bol
3131
stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold)
3232

3333
function repl_cmd(cmd, out)
34-
shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh")))
34+
shell = get(ENV, "JULIA_SHELL", nothing)
35+
if shell === nothing || isempty(shell)
36+
shell = Sys.iswindows() ? "cmd" : get(ENV, "SHELL", "/bin/sh")
37+
end
38+
shell = shell_split(shell)
3539
shell_name = Base.basename(shell[1])
40+
if Sys.iswindows()
41+
shell_name = lowercase(splitext(shell_name)[1]) # canonicalize for comparisons below
42+
end
3643

3744
# Immediately expand all arguments, so that typing e.g. ~/bin/foo works.
3845
cmd.exec .= expanduser.(cmd.exec)
@@ -66,15 +73,28 @@ function repl_cmd(cmd, out)
6673
ENV["OLDPWD"] = new_oldpwd
6774
println(out, pwd())
6875
else
69-
@static if !Sys.iswindows()
76+
local command::Cmd
77+
if Sys.iswindows()
78+
if shell_name == ""
79+
command = cmd
80+
elseif shell_name == "cmd"
81+
command = Cmd(`$shell /c $(shell_escape_CMDly(shell_escape_winsomely(cmd)))`, windows_verbatim=true)
82+
elseif shell_name in ("powershell", "pwsh")
83+
command = Cmd(`$shell -Command $(shell_escape_PWSHly(shell_escape_winsomely(cmd)))`, windows_verbatim=true)
84+
elseif shell_name == "busybox"
85+
command = `$shell sh -c $(shell_escape_posixly(cmd))`
86+
else
87+
command = `$shell $cmd`
88+
end
89+
else
7090
if shell_name == "fish"
7191
shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end"
7292
else
7393
shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true"
7494
end
75-
cmd = `$shell -c $shell_escape_cmd`
95+
command = `$shell -c $shell_escape_cmd`
7696
end
77-
run(ignorestatus(cmd))
97+
run(ignorestatus(command))
7898
end
7999
nothing
80100
end

base/shell.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,50 @@ julia> println(shell_escaped_winsomely("A B\\", "C"))
312312
"""
313313
shell_escape_winsomely(args::AbstractString...) =
314314
sprint(print_shell_escaped_winsomely, args..., sizehint=(sum(length, args)) + 3*length(args))
315+
316+
function print_shell_escaped_CMDly(io::IO, arg::AbstractString)
317+
any(in(c -> c in ('\r', '\n')), arg) && throw(ArgumentError("Encountered unsupported character by CMD."))
318+
# include " so to avoid toggling behavior of ^
319+
# the rule for ! is a bit esoteric, but doesn't hurt to add it
320+
arg = replace(arg, r"[%!^<>&|]" => s"^\0")
321+
print(io, arg)
322+
end
323+
324+
"""
325+
shell_escape_CMDly(arg::AbstractString)::String
326+
327+
The unexported `shell_escape_CMDly` function takes a string and escapes any special characters
328+
in such a way that it is safe to pass it as an argument to some `CMD.exe`. This may be useful
329+
in concert with the `windows_verbatim` flag to [`Cmd`](@ref) when constructing process
330+
pipelines.
331+
332+
See also [`shell_escape_BATCHly`](@ref) and [`shell_escape_PWSHly`](@ref).
333+
334+
# Example
335+
```jldoctest
336+
julia> println(shell_escape_CMDly("\"A B\\\" & C"))
337+
^"A B\\^" ^& C
338+
339+
!important
340+
Due to a peculiar behavior of the CMD, each command after a literal `|` character
341+
(indicating a command pipeline) must have `shell_escape_CMDly` applied twice. For example:
342+
```
343+
to_print = "All for 1 & 1 for all!"
344+
run(Cmd(Cmd(["cmd /c \"break | echo \$(shell_escape_CMDly(shell_escape_CMDly(to_print)))"]), windows_verbatim=true))
345+
```
346+
"""
347+
shell_escape_CMDly(arg::AbstractString) = sprint(print_shell_escaped_CMDly, arg)
348+
349+
function print_shell_escaped_PWSHly(io::IO, arg::AbstractString)
350+
arg = replace(arg, r"[`\$#;]" => s"`\0")
351+
print(io, arg)
352+
end
353+
354+
"""
355+
shell_escape_PWSHly(arg::AbstractString)::String
356+
357+
Escapes special characters so they can be appropriately used with PowerShell.
358+
359+
See also [`shell_escape_CMDly`](@ref).
360+
"""
361+
shell_escape_PWSHly(arg::AbstractString) = sprint(print_shell_escaped_PWSHly, arg)

doc/src/manual/environment-variables.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,9 @@ The absolute path of the shell with which Julia should execute external commands
168168
(via `Base.repl_cmd()`). Defaults to the environment variable `$SHELL`, and
169169
falls back to `/bin/sh` if `$SHELL` is unset.
170170

171-
!!! note
172-
173-
On Windows, this environment variable is ignored, and external commands are
174-
executed directly.
171+
On Windows, `$JULIA_SHELL` can be set to `cmd`, `powershell`, `busybox` or `""`.
172+
If set to `""` external commands are executed directly. Defaults to `cmd` if
173+
`$JULIA_SHELL` is not set.
175174

176175
### `JULIA_EDITOR`
177176

stdlib/REPL/docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ julia> ; # upon typing ;, the prompt changes (in place) to: shell>
103103
shell> echo hello
104104
hello
105105
```
106+
See `JULIA_SHELL` in the Environment Variables section of the Julia manual.
106107

107108
### Search modes
108109

0 commit comments

Comments
 (0)