22
33# # shell-like command parsing ##
44
5- function shell_parse (raw:: AbstractString , interp:: Bool )
6- s = lstrip (raw)
7- # Strips the end but respects the space when the string endswith "\\ "
5+ const shell_special = " #{}()[]<>|&*?~;"
6+
7+ function shell_parse (str:: AbstractString , interpolate:: Bool = true )
8+ s = lstrip (str)
9+ # strips the end but respects the space when the string ends with "\\ "
810 r = RevString (s)
911 i = start (r)
1012 c_old = nothing
@@ -22,7 +24,7 @@ function shell_parse(raw::AbstractString, interp::Bool)
2224 s = s[1 : end - i+ 1 ]
2325
2426 last_parse = 0 : - 1
25- isempty (s) && return interp ? (Expr (:tuple ,:()),last_parse) : ([],last_parse)
27+ isempty (s) && return interpolate ? (Expr (:tuple ,:()),last_parse) : ([],last_parse)
2628
2729 in_single_quotes = false
2830 in_double_quotes = false
@@ -57,7 +59,7 @@ function shell_parse(raw::AbstractString, interp::Bool)
5759 end
5860 j = k
5961 end
60- elseif interp && ! in_single_quotes && c == ' $'
62+ elseif interpolate && ! in_single_quotes && c == ' $'
6163 update_arg (s[i: j- 1 ]); i = k; j = k
6264 if done (s,k)
6365 error (" \$ right before end of command" )
@@ -92,6 +94,8 @@ function shell_parse(raw::AbstractString, interp::Bool)
9294 update_arg (s[i: j- 1 ]); i = k
9395 c, k = next (s,k)
9496 end
97+ elseif ! in_single_quotes && ! in_double_quotes && c in shell_special
98+ depwarn (" special characters \" $shell_special \" should now be quoted in commands" , :shell_parse )
9599 end
96100 j = k
97101 end
@@ -103,18 +107,15 @@ function shell_parse(raw::AbstractString, interp::Bool)
103107 update_arg (s[i: end ])
104108 append_arg ()
105109
106- if ! interp
107- return (args,last_parse)
108- end
110+ interpolate || return args, last_parse
109111
110112 # construct an expression
111113 ex = Expr (:tuple )
112114 for arg in args
113115 push! (ex. args, Expr (:tuple , arg... ))
114116 end
115- ( ex,last_parse)
117+ return ex, last_parse
116118end
117- shell_parse (s:: AbstractString ) = shell_parse (s,true )
118119
119120function shell_split (s:: AbstractString )
120121 parsed = shell_parse (s,false )[1 ]
@@ -125,14 +126,14 @@ function shell_split(s::AbstractString)
125126 args
126127end
127128
128- function print_shell_word (io:: IO , word:: AbstractString )
129+ function print_shell_word (io:: IO , word:: AbstractString , special :: AbstractString = shell_special )
129130 if isempty (word)
130131 print (io, " ''" )
131132 end
132133 has_single = false
133134 has_special = false
134135 for c in word
135- if isspace (c) || c== ' \\ ' || c== ' \' ' || c== ' "' || c== ' $'
136+ if isspace (c) || c== ' \\ ' || c== ' \' ' || c== ' "' || c== ' $' || c in special
136137 has_special = true
137138 if c == ' \' '
138139 has_single = true
@@ -155,13 +156,32 @@ function print_shell_word(io::IO, word::AbstractString)
155156 end
156157end
157158
158- function print_shell_escaped (io:: IO , cmd:: AbstractString , args:: AbstractString... )
159- print_shell_word (io, cmd)
159+ function print_shell_escaped (
160+ io:: IO , cmd:: AbstractString , args:: AbstractString... ;
161+ special:: AbstractString = shell_special
162+ )
163+ print_shell_word (io, cmd, special)
160164 for arg in args
161165 print (io, ' ' )
162- print_shell_word (io, arg)
166+ print_shell_word (io, arg, special )
163167 end
164168end
165- print_shell_escaped (io:: IO ) = nothing
169+ print_shell_escaped (io:: IO ; special:: String = shell_special) = nothing
170+
171+ """
172+ shell_escape(args::Union{Cmd,AbstractString...}; special::AbstractString="$shell_special ")
173+
174+ The unexported `shell_escape` function is the inverse of the unexported `shell_split` function:
175+ it takes a string or command object and escapes any special characters in such a way that calling
176+ `shell_split` on it would give back the array of words in the original command. The `special`
177+ keyword argument controls what characters in addition to whitespace, backslashes, quotes and
178+ dollar signs are considered to be special. Examples:
179+
180+ julia> Base.shell_escape("echo", "this", "&&", "that")
181+ "echo this '&&' that"
166182
167- shell_escape (args:: AbstractString... ) = sprint (print_shell_escaped, args... )
183+ julia> Base.shell_escape("cat", "/foo/bar baz", "&&", "echo", "done", special="")
184+ "cat '/foo/bar baz' && echo done"
185+ """
186+ shell_escape (args:: AbstractString... ; special:: AbstractString = shell_special) =
187+ sprint (io-> print_shell_escaped (io, args... , special= special))
0 commit comments