diff --git a/base/client.jl b/base/client.jl index 5bf658bead437..54f5313d513ea 100644 --- a/base/client.jl +++ b/base/client.jl @@ -31,7 +31,38 @@ answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answ stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bold) stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold) -function repl_cmd(cmd, out) +""" + ShellSpecification{is_windows, shell} + +A type used for dispatch to select the appropriate shell command preparation logic. +It is parameterized by `is_windows::Bool` indicating the operating system, +and `shell::Symbol` representing the basename of the shell executable. +""" +struct ShellSpecification{is_windows,shell} end + +""" + prepare_shell_command(spec::ShellSpecification, cmd::Cmd, raw_string::String) -> Cmd + +Returns a `Cmd` object configured for execution according to `spec`, +using the provided `cmd` (parsed command) and `raw_string` (original input). +Specialized methods for `ShellSpecification` define shell- and OS-specific behavior. +""" +function prepare_shell_command(::ShellSpecification{true,shell}, cmd, _) where {shell} + return cmd +end +function prepare_shell_command(::ShellSpecification{false,shell}, cmd, _) where {shell} + shell_escape_cmd = "$(shell_escape_posixly(cmd)) && true" + return `$shell -c $shell_escape_cmd` +end +function prepare_shell_command(::ShellSpecification{false,:fish}, cmd, _) + shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" + return `fish -c $shell_escape_cmd` +end +function prepare_shell_command(::ShellSpecification{false,:nu}, _, raw_string) + return `nu -c $raw_string` +end + +function repl_cmd(cmd, raw_string, out) shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh"))) shell_name = Base.basename(shell[1]) @@ -64,16 +95,10 @@ function repl_cmd(cmd, out) cd(dir) println(out, pwd()) else - @static if !Sys.iswindows() - 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` - end + shell_spec = ShellSpecification{Sys.iswindows(),Symbol(shell_name)}() + prepared_cmd = prepare_shell_command(shell_spec, cmd, raw_string) try - run(ignorestatus(cmd)) + run(ignorestatus(prepared_cmd)) catch # Windows doesn't shell out right now (complex issue), so Julia tries to run the program itself # Julia throws an exception if it can't find the program, but the stack trace isn't useful @@ -85,6 +110,9 @@ function repl_cmd(cmd, out) nothing end +# For backward compatibility +repl_cmd(cmd, out) = repl_cmd(cmd, string(cmd), out) + # deprecated function--preserved for DocTests.jl function ip_matches_func(ip, func::Symbol) for fr in StackTraces.lookup(ip) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 81272ac971d40..af52c9b94038e 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1335,6 +1335,7 @@ function setup_interface( on_done = respond(repl, julia_prompt) do line Expr(:call, :(Base.repl_cmd), :(Base.cmd_gen($(Base.shell_parse(line::String)[1]))), + line::String, outstream(repl)) end, sticky = true) diff --git a/test/file.jl b/test/file.jl index a163bc07034ab..c91a26225847b 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1925,15 +1925,15 @@ end cd(dir) do withenv("OLDPWD" => nothing) do io = IOBuffer() - Base.repl_cmd(@cmd("cd"), io) - Base.repl_cmd(@cmd("cd -"), io) + Base.repl_cmd(@cmd("cd"), "cd", io) + Base.repl_cmd(@cmd("cd -"), "cd -", io) @test realpath(pwd()) == realpath(dir) if !Sys.iswindows() # Delete the working directory and check we can cd out of it # Cannot delete the working directory on Windows rm(dir) @test_throws Base._UVError("pwd()", Base.UV_ENOENT) pwd() - Base.repl_cmd(@cmd("cd \\~"), io) + Base.repl_cmd(@cmd("cd \\~"), "cd \\~", io) end end end