Skip to content

Commit

Permalink
REPL shell mode for Windows
Browse files Browse the repository at this point in the history
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
  • Loading branch information
GregPlowman authored and musm committed Oct 5, 2019
1 parent 7e6f236 commit c60ba50
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 9 deletions.
2 changes: 1 addition & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ include("iobuffer.jl")
include("intfuncs.jl")
include("strings/strings.jl")
include("parse.jl")
include("shell.jl")
include("regex.jl")
include("shell.jl")
include("show.jl")
include("arrayshow.jl")
include("methodshow.jl")
Expand Down
24 changes: 20 additions & 4 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bol
stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold)

function repl_cmd(cmd, out)
shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh")))
shell = shell_split(get(ENV, "JULIA_SHELL", Sys.iswindows() ? "cmd" : get(ENV, "SHELL", "/bin/sh")))
shell_name = Base.basename(shell[1])
if Sys.iswindows()
shell_name = lowercase(splitext(shell_name)[1]) # canonicalize for comparisons below
end

# Immediately expand all arguments, so that typing e.g. ~/bin/foo works.
cmd.exec .= expanduser.(cmd.exec)
Expand Down Expand Up @@ -66,15 +69,28 @@ function repl_cmd(cmd, out)
ENV["OLDPWD"] = new_oldpwd
println(out, pwd())
else
@static if !Sys.iswindows()
local command::Cmd
if Sys.iswindows()
if shell_name == ""
command = cmd
elseif shell_name == "cmd"
command = Cmd(`$shell /c $(shell_escape_CMDly(shell_escape_winsomely(cmd)))`, windows_verbatim=true)
elseif shell_name in ("powershell", "pwsh")
command = Cmd(`$shell -Command $(shell_escape_PWSHly(shell_escape_winsomely(cmd)))`, windows_verbatim=true)
elseif shell_name == "busybox"
command = `$shell sh -c $(shell_escape_posixly(cmd))`
else
command = `$shell $cmd`
end
else
if shell_name == "fish"
shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end"
else
shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true"
end
cmd = `$shell -c $shell_escape_cmd`
command = `$shell -c $shell_escape_cmd`
end
run(ignorestatus(cmd))
run(ignorestatus(command))
end
nothing
end
Expand Down
67 changes: 67 additions & 0 deletions base/shell.jl
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,70 @@ julia> println(shell_escaped_winsomely("A B\\", "C"))
"""
shell_escape_winsomely(args::AbstractString...) =
sprint(print_shell_escaped_winsomely, args..., sizehint=(sum(length, args)) + 3*length(args))

function print_shell_escaped_CMDly(io::IO, arg::AbstractString)
any(in("\r\n"), arg) && throw("Encountered unsupported character by CMD.")
# include " so to avoid toggling behavior of ^
# the rule for ! is a bit esoteric, but doesn't hurt to add it
arg = replace(arg, r"[%!^<>&|]" => s"^\0")
print(io, arg)
end

"""
shell_escape_CMDly(arg::AbstractString)::String
The unexported `shell_escape_CMDly` function takes a string and escapes any special characters
in such a way that it is safe to pass it as an argument to some `CMD.exe`. This may be useful
in concert with the `windows_verbatim` flag to [`Cmd`](@ref) when constructing process
pipelines.
See also [`shell_escape_BATCHly`](@ref) and [`shell_escape_PWSHly`](@ref).
# Example
```jldoctest
julia> println(shell_escape_CMDly("\"A B\\\" & C"))
^"A B\\^" ^& C
!important
Due to a peculiar behavior of the CMD, each command after a literal `|` character
(indicating a command pipeline) must have `shell_escape_CMDly` applied twice. For example:
```
to_print = "All for 1 & 1 for all!"
run(Cmd(Cmd(["cmd /c \"break | echo \$(shell_escape_CMDly(shell_escape_CMDly(to_print)))"]), windows_verbatim=true))
```
"""
shell_escape_CMDly(arg::AbstractString) = sprint(print_shell_escaped_CMDly, arg)

function print_shell_escaped_BATCHly(io::IO, arg::AbstractString)
# see https://www.robvanderwoude.com/variableexpansion.php
# the rule for ! is a bit esoteric, but doesn't hurt to add it
any(in("\r\n"), arg) && throw("Encountered unsupported character by CMD")
arg = replace(arg, r"[()!^\"<>&|]" => s"^\0")
arg = replace(arg, "%" => "%%")
print(io, arg)
end

"""
shell_escape_BATCHly(arg::AbstractString)::String
Like [`shell_escape_CMDly`](@ref), but appropriate for use inside batch (*.bat) files for use
with `CMD.exe`.
See also [`shell_escape_PWSHly`](@ref).
"""
shell_escape_BATCHly(arg::AbstractString) = sprint(print_shell_escaped_BATCHly, arg)


function print_shell_escaped_PWSHly(io::IO, arg::AbstractString)
arg = replace(arg, r"`\$#;@" => s"`\0")
print(io, arg)
end

"""
shell_escape_PWSHly(arg::AbstractString)::String
Escapes special characters so they can be appropriately used with PowerShell.
See also [`shell_escape_CMDly`](@ref).
"""
shell_escape_PWSHly(arg::AbstractString) = sprint(print_shell_escaped_PWSHly, arg)
7 changes: 3 additions & 4 deletions doc/src/manual/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,9 @@ The absolute path of the shell with which Julia should execute external commands
(via `Base.repl_cmd()`). Defaults to the environment variable `$SHELL`, and
falls back to `/bin/sh` if `$SHELL` is unset.

!!! note

On Windows, this environment variable is ignored, and external commands are
executed directly.
On Windows, `$JULIA_SHELL` can be set to `cmd`, `powershell`, `busybox` or `""`.
If set to `""` external commands are executed directly. Defaults to `cmd` if
`$JULIA_SHELL` is not set.

### `JULIA_EDITOR`

Expand Down
1 change: 1 addition & 0 deletions stdlib/REPL/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ julia> ; # upon typing ;, the prompt changes (in place) to: shell>
shell> echo hello
hello
```
See `JULIA_SHELL` in the Environment Variables section of the Julia manual.

### Search modes

Expand Down

0 comments on commit c60ba50

Please sign in to comment.