Currently, in Julia the run
function is not as stable as the rest of the Julia.
You can’t run external commands with special characters. The different behavior of Cmd
from String
makes using it inconvenient. Windows support is not like other operating systems.
Running something as simple as the following in Julia requires a lot of workarounds.
# python
import os
os.system("echo hey/*")
os.system("echo hey && echo bye")
x="echo bye"
os.system(x)
Trying these in Julia:
- Running Commands with special characters:
using Cmd
direcrtly fails
julia> run(`echo hey/*`)
ERROR: LoadError: parsing command `echo hey/*`: special characters "#{}()[]<>|&*?~;" must be quoted in commands
Stacktrace:
[1] error(::String) at .\error.jl:33
[2] shell_parse(::String, ::Bool; special::String) at .\shell.jl:100
[3] @cmd(::LineNumberNode, ::Module, ::Any) at .\cmd.jl:389
in expression starting at REPL[3]:1
Using @cmd
fails
julia> @cmd run"echo hey/*"
ERROR: LoadError: MethodError: no method matching shell_parse(::Expr; special="#{}()[]<>|&*?~;")
Closest candidates are:
shell_parse(::AbstractString) at shell.jl:18 got unsupported keyword argument "special"
shell_parse(::AbstractString, ::Bool; special) at shell.jl:18
Stacktrace:
[1] @cmd(::LineNumberNode, ::Module, ::Any) at .\cmd.jl:389
in expression starting at REPL[6]:1
There is no @raw_cmd
and @raw_string
doesn’t work as stated in 3
julia> run(raw`echo hey/*`)
ERROR: LoadError: UndefVarError: @raw_cmd not defined
in expression starting at REPL[1]:1
julia> run(raw"echo hey/*")
ERROR: MethodError: no method matching run(::String)
Closest candidates are:
run(::Base.AbstractCmd, ::Any...; wait) at process.jl:437
Stacktrace:
[1] top-level scope at REPL[2]:1
julia> run(`$(raw"echo hey/*")`)
ERROR: IOError: could not spawn `'echo hey/*'`: no such file or directory (ENOENT)
Stacktrace:
[1] _spawn_primitive(::String, ::Cmd, ::Array{Any,1}) at .\process.jl:99
[2] #585 at .\process.jl:112 [inlined]
[3] setup_stdios(::Base.var"#585#586"{Cmd}, ::Array{Any,1}) at .\process.jl:196
[4] _spawn at .\process.jl:111 [inlined]
[5] run(::Cmd; wait::Bool) at .\process.jl:439
[6] run(::Cmd) at .\process.jl:438
[7] top-level scope at REPL[1]:1
In some cases, it is possible to explicitly escape the special characters but not always
# this works
julia> run(`echo hey/\*`)
hey/*
Process(`echo 'hey/*'`, ProcessExited(0))
# this doesn't
julia> run(`echo hi \&\& echo bye`) # or run(`echo hi '&&' echo bye`)
hi && echo bye
Process(`echo hi '&&' echo bye`, ProcessExited(0))
- Out of the box Windows Support
This requires another hackery if you want to run this on Windows:
# happens in pwsh, powershell, cmd
julia> run(`echo hey/\*`)
ERROR: IOError: could not spawn `echo 'hey/*'`: no such file or directory (ENOENT)
Stacktrace:
[1] _spawn_primitive(::String, ::Cmd, ::Array{Any,1}) at .\process.jl:99
[2] #585 at .\process.jl:112 [inlined]
[3] setup_stdios(::Base.var"#585#586"{Cmd}, ::Array{Any,1}) at .\process.jl:196
[4] _spawn at .\process.jl:111 [inlined]
[5] run(::Cmd; wait::Bool) at .\process.jl:439
[6] run(::Cmd) at .\process.jl:438
[7] top-level scope at REPL[1]:1
julia> run(`cmd /c echo hey/\*`)
hey/*
Process(`cmd /c echo 'hey/*'`, ProcessExited(0))
- Possibility of using
String
for building a command
julia> x = "echo bye"
"echo bye"
Using String
directly fails
julia> Cmd(x)
ERROR: MethodError: no method matching Cmd(::String)
Closest candidates are:
Cmd(::Array{String,1}) at cmd.jl:16
Cmd(::Cmd; ignorestatus, env, dir, detach, windows_verbatim, windows_hide) at cmd.jl:21
Cmd(::Cmd, ::Any, ::Any, ::Any, ::Any) at cmd.jl:18
Stacktrace:
[1] top-level scope at REPL[40]:1
Trying interpolation
julia> run(`$x`)
ERROR: IOError: could not spawn `'echo bye'`: no such file or directory (ENOENT)
Stacktrace:
[1] _spawn_primitive(::String, ::Cmd, ::Array{Any,1}) at .\process.jl:99
[2] #585 at .\process.jl:112 [inlined]
[3] setup_stdios(::Base.var"#585#586"{Cmd}, ::Array{Any,1}) at .\process.jl:196
[4] _spawn at .\process.jl:111 [inlined]
[5] run(::Cmd; wait::Bool) at .\process.jl:439
[6] run(::Cmd) at .\process.jl:438
[7] top-level scope at REPL[31]:1
Trying @cmd
julia> @cmd($x)
ERROR: LoadError: MethodError: no method matching shell_parse(::Expr; special="#{}()[]<>|&*?~;")
Closest candidates are:
shell_parse(::AbstractString) at shell.jl:18 got unsupported keyword argument "special"
shell_parse(::AbstractString, ::Bool; special) at shell.jl:18
Stacktrace:
[1] @cmd(::LineNumberNode, ::Module, ::Any) at .\cmd.jl:389
in expression starting at REPL[36]:1
julia> @cmd(x)
ERROR: LoadError: MethodError: no method matching shell_parse(::Symbol; special="#{}()[]<>|&*?~;")
Closest candidates are:
shell_parse(::AbstractString) at shell.jl:18 got unsupported keyword argument "special"
shell_parse(::AbstractString, ::Bool; special) at shell.jl:18
Stacktrace:
[1] @cmd(::LineNumberNode, ::Module, ::Any) at .\cmd.jl:389
in expression starting at REPL[37]:1
julia> @cmd("$x")
ERROR: LoadError: MethodError: no method matching shell_parse(::Expr; special="#{}()[]<>|&*?~;")
Closest candidates are:
shell_parse(::AbstractString) at shell.jl:18 got unsupported keyword argument "special"
shell_parse(::AbstractString, ::Bool; special) at shell.jl:18
Stacktrace:
[1] @cmd(::LineNumberNode, ::Module, ::Any) at .\cmd.jl:389
in expression starting at REPL[39]:1
My suggestion for 1 and 3: allow using String
with run
. This will fix 1 with the help of @raw_string
. This will also fix 3 by directly sending the command to the system without putting '
around it.
My suggestion for 2: detect the host shell and adjust run
accordignly. If not possible define a run2
which automatically finds pwsh
, powershell
, or cmd
. In Julia 2, make run2
the default run
.