• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38162

06 Aug 2025 08:25PM UTC coverage: 25.688% (-43.6%) from 69.336%
#38162

push

local

web-flow
fix runtime cglobal builtin function implementation (#59210)

This had failed to be updated for the LazyLibrary changes to codegen.

12976 of 50513 relevant lines covered (25.69%)

676965.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.1
/base/shell.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
## shell-like command parsing ##
4

5
const shell_special = "#{}()[]<>|&*?~;"
6

7
(@doc raw"""
8
    rstrip_shell(s::AbstractString)
9

10
Strip trailing whitespace from a shell command string, while respecting a trailing backslash followed by a space ("\\ ").
11

12
```jldoctest
13
julia> Base.rstrip_shell("echo 'Hello World' \\ ")
14
"echo 'Hello World' \\ "
15

16
julia> Base.rstrip_shell("echo 'Hello World'    ")
17
"echo 'Hello World'"
18
```
19
"""
20
function rstrip_shell(s::AbstractString)
5✔
21
    c_old = nothing
5✔
22
    for (i, c) in Iterators.reverse(pairs(s))
5✔
23
        i::Int; c::AbstractChar
4✔
24
        ((c == '\\') && c_old == ' ') && return SubString(s, 1, i+1)
4✔
25
        isspace(c) || return SubString(s, 1, i)
8✔
26
        c_old = c
×
27
    end
×
28
    SubString(s, 1, 0)
1✔
29
end)
30

31
function shell_parse(str::AbstractString, interpolate::Bool=true;
15✔
32
                     special::AbstractString="", filename="none")
33
    last_arg = firstindex(str) # N.B.: This is used by REPLCompletions
5✔
34
    s = SubString(str, last_arg)
9✔
35
    s = rstrip_shell(lstrip(s))
5✔
36

37
    isempty(s) && return interpolate ? (Expr(:tuple,:()), last_arg) : ([], last_arg)
5✔
38

39
    in_single_quotes = false
4✔
40
    in_double_quotes = false
4✔
41

42
    args = []
4✔
43
    arg = []
4✔
44
    i = firstindex(s)
4✔
45
    st = Iterators.Stateful(pairs(s))
8✔
46
    update_last_arg = false # true after spaces or interpolate
4✔
47

48
    function push_nonempty!(list, x)
4✔
49
        if !isa(x,AbstractString) || !isempty(x)
10✔
50
            push!(list, x)
×
51
        end
52
        return nothing
10✔
53
    end
54
    function consume_upto!(list, s, i, j)
10✔
55
        push_nonempty!(list, s[i:prevind(s, j)::Int])
6✔
56
        something(peek(st), lastindex(s)::Int+1 => '\0').first::Int
6✔
57
    end
58
    function append_2to1!(list, innerlist)
9✔
59
        if isempty(innerlist); push!(innerlist, ""); end
5✔
60
        push!(list, copy(innerlist))
10✔
61
        empty!(innerlist)
5✔
62
    end
63

64
    C = eltype(str)
4✔
65
    P = Pair{Int,C}
4✔
66
    for (j, c) in st
8✔
67
        j, c = j::Int, c::C
6✔
68
        if !in_single_quotes && !in_double_quotes && isspace(c)
11✔
69
            update_last_arg = true
1✔
70
            i = consume_upto!(arg, s, i, j)
1✔
71
            append_2to1!(args, arg)
1✔
72
            while !isempty(st)
1✔
73
                # We've made sure above that we don't end in whitespace,
74
                # so updating `i` here is ok
75
                (i, c) = peek(st)::P
1✔
76
                isspace(c) || break
2✔
77
                popfirst!(st)
×
78
            end
×
79
        elseif interpolate && !in_single_quotes && c == '$'
5✔
80
            i = consume_upto!(arg, s, i, j)
5✔
81
            isempty(st) && error("\$ right before end of command")
5✔
82
            stpos, c = popfirst!(st)::P
10✔
83
            isspace(c) && error("space not allowed right after \$")
10✔
84
            if startswith(SubString(s, stpos), "var\"")
5✔
85
                # Disallow var"#" syntax in cmd interpolations.
86
                # TODO: Allow only identifiers after the $ for consistency with
87
                # string interpolation syntax (see #3150)
88
                ex, j = :var, stpos+3
×
89
            else
90
                # use parseatom instead of parse to respect filename (#28188)
91
                ex, j = Meta.parseatom(s, stpos, filename=filename)
5✔
92
            end
93
            last_arg = stpos + s.offset
5✔
94
            update_last_arg = true
5✔
95
            push!(arg, ex)
5✔
96
            s = SubString(s, j)
5✔
97
            Iterators.reset!(st, pairs(s))
6✔
98
            i = firstindex(s)
5✔
99
        else
100
            if update_last_arg
×
101
                last_arg = i + s.offset
×
102
                update_last_arg = false
×
103
            end
104
            if !in_double_quotes && c == '\''
×
105
                in_single_quotes = !in_single_quotes
×
106
                i = consume_upto!(arg, s, i, j)
×
107
            elseif !in_single_quotes && c == '"'
×
108
                in_double_quotes = !in_double_quotes
×
109
                i = consume_upto!(arg, s, i, j)
×
110
            elseif !in_single_quotes && c == '\\'
×
111
                if !isempty(st) && (peek(st)::P)[2] in ('\n', '\r')
×
112
                    i = consume_upto!(arg, s, i, j) + 1
×
113
                    if popfirst!(st)[2] == '\r' && (peek(st)::P)[2] == '\n'
×
114
                        i += 1
×
115
                        popfirst!(st)
×
116
                    end
117
                    while !isempty(st) && (peek(st)::P)[2] in (' ', '\t')
×
118
                        i = nextind(str, i)
×
119
                        _ = popfirst!(st)
×
120
                    end
×
121
                elseif in_double_quotes
×
122
                    isempty(st) && error("unterminated double quote")
×
123
                    k, c′ = peek(st)::P
×
124
                    if c′ == '"' || c′ == '$' || c′ == '\\'
×
125
                        i = consume_upto!(arg, s, i, j)
×
126
                        _ = popfirst!(st)
×
127
                    end
128
                else
129
                    isempty(st) && error("dangling backslash")
×
130
                    i = consume_upto!(arg, s, i, j)
×
131
                    _ = popfirst!(st)
×
132
                end
133
            elseif !in_single_quotes && !in_double_quotes && c in special
×
134
                error("parsing command `$str`: special characters \"$special\" must be quoted in commands")
×
135
            end
136
        end
137
    end
8✔
138

139
    if in_single_quotes; error("unterminated single quote"); end
4✔
140
    if in_double_quotes; error("unterminated double quote"); end
4✔
141

142
    push_nonempty!(arg, s[i:end])
4✔
143
    append_2to1!(args, arg)
4✔
144

145
    interpolate || return args, last_arg
4✔
146

147
    # construct an expression
148
    ex = Expr(:tuple)
4✔
149
    for arg in args
4✔
150
        push!(ex.args, Expr(:tuple, arg...))
5✔
151
    end
5✔
152
    return ex, last_arg
4✔
153
end
154

155
"""
156
    shell_split(command::AbstractString)
157

158
Split a shell command string into its individual components.
159

160
# Examples
161
```jldoctest
162
julia> Base.shell_split("git commit -m 'Initial commit'")
163
4-element Vector{String}:
164
 "git"
165
 "commit"
166
 "-m"
167
 "Initial commit"
168
```
169
"""
170
function shell_split(s::AbstractString)
×
171
    parsed = shell_parse(s, false)[1]
×
172
    args = String[]
×
173
    for arg in parsed
×
174
        push!(args, string(arg...))
×
175
    end
×
176
    args
×
177
end
178

179
function print_shell_word(io::IO, word::AbstractString, special::AbstractString = "")
13✔
180
    has_single = false
13✔
181
    has_special = false
13✔
182
    for c in word
26✔
183
        if isspace(c) || c=='\\' || c=='\'' || c=='"' || c=='$' || c in special
864✔
184
            has_special = true
×
185
            if c == '\''
×
186
                has_single = true
×
187
            end
188
        end
189
    end
851✔
190
    if isempty(word)
13✔
191
        print(io, "''")
×
192
    elseif !has_special
13✔
193
        print(io, word)
13✔
194
    elseif !has_single
×
195
        print(io, '\'', word, '\'')
×
196
    else
197
        print(io, '"')
×
198
        for c in word
×
199
            if c == '"' || c == '$'
×
200
                print(io, '\\')
×
201
            end
202
            print(io, c)
×
203
        end
×
204
        print(io, '"')
×
205
    end
206
    nothing
13✔
207
end
208

209
function print_shell_escaped(io::IO, cmd::AbstractString, args::AbstractString...;
×
210
                             special::AbstractString="")
211
    print_shell_word(io, cmd, special)
×
212
    for arg in args
×
213
        print(io, ' ')
×
214
        print_shell_word(io, arg, special)
×
215
    end
×
216
end
217
print_shell_escaped(io::IO; special::String="") = nothing
×
218

219
"""
220
    shell_escape(args::Union{Cmd,AbstractString...}; special::AbstractString="")
221

222
The unexported `shell_escape` function is the inverse of the unexported [`Base.shell_split()`](@ref) function:
223
it takes a string or command object and escapes any special characters in such a way that calling
224
[`Base.shell_split()`](@ref) on it would give back the array of words in the original command. The `special`
225
keyword argument controls what characters in addition to whitespace, backslashes, quotes and
226
dollar signs are considered to be special (default: none).
227

228
# Examples
229
```jldoctest
230
julia> Base.shell_escape("cat", "/foo/bar baz", "&&", "echo", "done")
231
"cat '/foo/bar baz' && echo done"
232

233
julia> Base.shell_escape("echo", "this", "&&", "that")
234
"echo this && that"
235
```
236
"""
237
shell_escape(args::AbstractString...; special::AbstractString="") =
×
238
    sprint((io, args...) -> print_shell_escaped(io, args..., special=special), args...)
×
239

240

241
function print_shell_escaped_posixly(io::IO, args::AbstractString...)
×
242
    first = true
×
243
    for arg in args
×
244
        first || print(io, ' ')
×
245
        # avoid printing quotes around simple enough strings
246
        # that any (reasonable) shell will definitely never consider them to be special
247
        have_single::Bool = false
×
248
        have_double::Bool = false
×
249
        function isword(c::AbstractChar)
×
250
            if '0' <= c <= '9' || 'a' <= c <= 'z' || 'A' <= c <= 'Z'
×
251
                # word characters
252
            elseif c == '_' || c == '/' || c == '+' || c == '-' || c == '.'
×
253
                # other common characters
254
            elseif c == '\''
×
255
                have_single = true
×
256
            elseif c == '"'
×
257
                have_double && return false # switch to single quoting
×
258
                have_double = true
×
259
            elseif !first && c == '='
×
260
                # equals is special if it is first (e.g. `env=val ./cmd`)
261
            else
262
                # anything else
263
                return false
×
264
            end
265
            return true
×
266
        end
267
        if isempty(arg)
×
268
            print(io, "''")
×
269
        elseif all(isword, arg)
×
270
            have_single && (arg = replace(arg, '\'' => "\\'"))
×
271
            have_double && (arg = replace(arg, '"' => "\\\""))
×
272
            print(io, arg)
×
273
        else
274
            print(io, '\'', replace(arg, '\'' => "'\\''"), '\'')
×
275
        end
276
        first = false
×
277
    end
×
278
end
279

280
"""
281
    shell_escape_posixly(args::Union{Cmd,AbstractString...})
282

283
The unexported `shell_escape_posixly` function
284
takes a string or command object and escapes any special characters in such a way that
285
it is safe to pass it as an argument to a posix shell.
286

287
See also: [`Base.shell_escape()`](@ref)
288

289
# Examples
290
```jldoctest
291
julia> Base.shell_escape_posixly("cat", "/foo/bar baz", "&&", "echo", "done")
292
"cat '/foo/bar baz' '&&' echo done"
293

294
julia> Base.shell_escape_posixly("echo", "this", "&&", "that")
295
"echo this '&&' that"
296
```
297
"""
298
shell_escape_posixly(args::AbstractString...) =
×
299
    sprint(print_shell_escaped_posixly, args...)
300

301
"""
302
    shell_escape_csh(args::Union{Cmd,AbstractString...})
303
    shell_escape_csh(io::IO, args::Union{Cmd,AbstractString...})
304

305
This function quotes any metacharacters in the string arguments such
306
that the string returned can be inserted into a command-line for
307
interpretation by the Unix C shell (csh, tcsh), where each string
308
argument will form one word.
309

310
In contrast to a POSIX shell, csh does not support the use of the
311
backslash as a general escape character in double-quoted strings.
312
Therefore, this function wraps strings that might contain
313
metacharacters in single quotes, except for parts that contain single
314
quotes, which it wraps in double quotes instead. It switches between
315
these types of quotes as needed. Linefeed characters are escaped with
316
a backslash.
317

318
This function should also work for a POSIX shell, except if the input
319
string contains a linefeed (`"\\n"`) character.
320

321
See also: [`Base.shell_escape_posixly()`](@ref)
322
"""
323
function shell_escape_csh(io::IO, args::AbstractString...)
×
324
    first = true
×
325
    for arg in args
×
326
        first || write(io, ' ')
×
327
        first = false
×
328
        i = 1
×
329
        while true
×
330
            for (r,e) = (r"^[A-Za-z0-9/\._-]+\z"sa => "",
×
331
                         r"^[^']*\z"sa => "'", r"^[^\$\`\"]*\z"sa => "\"",
×
332
                         r"^[^']+"sa  => "'", r"^[^\$\`\"]+"sa  => "\"")
×
333
                if ((m = match(r, SubString(arg, i))) !== nothing)
×
334
                    write(io, e)
×
335
                    write(io, replace(m.match, '\n' => "\\\n"))
×
336
                    write(io, e)
×
337
                    i += ncodeunits(m.match)
×
338
                    break
×
339
                end
340
            end
×
341
            i <= lastindex(arg) || break
×
342
        end
×
343
    end
×
344
end
345
shell_escape_csh(args::AbstractString...) =
×
346
    sprint(shell_escape_csh, args...;
347
           sizehint = sum(sizeof, args) + length(args) * 3)
348

349
"""
350
    shell_escape_wincmd(s::AbstractString)
351
    shell_escape_wincmd(io::IO, s::AbstractString)
352

353
The unexported `shell_escape_wincmd` function escapes Windows `cmd.exe` shell
354
meta characters. It escapes `()!^<>&|` by placing a `^` in front. An `@` is
355
only escaped at the start of the string. Pairs of `"` characters and the
356
strings they enclose are passed through unescaped. Any remaining `"` is escaped
357
with `^` to ensure that the number of unescaped `"` characters in the result
358
remains even.
359

360
Since `cmd.exe` substitutes variable references (like `%USER%`) _before_
361
processing the escape characters `^` and `"`, this function makes no attempt to
362
escape the percent sign (`%`), the presence of `%` in the input may cause
363
severe breakage, depending on where the result is used.
364

365
Input strings with ASCII control characters that cannot be escaped (NUL, CR,
366
LF) will cause an `ArgumentError` exception.
367

368
The result is safe to pass as an argument to a command call being processed by
369
`CMD.exe /S /C " ... "` (with surrounding double-quote pair) and will be
370
received verbatim by the target application if the input does not contain `%`
371
(else this function will fail with an ArgumentError). The presence of `%` in
372
the input string may result in command injection vulnerabilities and may
373
invalidate any claim of suitability of the output of this function for use as
374
an argument to cmd (due to the ordering described above), so use caution when
375
assembling a string from various sources.
376

377
This function may be useful in concert with the `windows_verbatim` flag to
378
[`Cmd`](@ref) when constructing process pipelines.
379

380
```julia
381
wincmd(c::String) =
382
   run(Cmd(Cmd(["cmd.exe", "/s /c \\" \$c \\""]);
383
           windows_verbatim=true))
384
wincmd_echo(s::String) =
385
   wincmd("echo " * Base.shell_escape_wincmd(s))
386
wincmd_echo("hello \$(ENV["USERNAME"]) & the \\"whole\\" world! (=^I^=)")
387
```
388

389
But take note that if the input string `s` contains a `%`, the argument list
390
and echo'ed text may get corrupted, resulting in arbitrary command execution.
391
The argument can alternatively be passed as an environment variable, which
392
avoids the problem with `%` and the need for the `windows_verbatim` flag:
393

394
```julia
395
cmdargs = Base.shell_escape_wincmd("Passing args with %cmdargs% works 100%!")
396
run(setenv(`cmd /C echo %cmdargs%`, "cmdargs" => cmdargs))
397
```
398

399
!!! warning
400
    The argument parsing done by CMD when calling batch files (either inside
401
    `.bat` files or as arguments to them) is not fully compatible with the
402
    output of this function. In particular, the processing of `%` is different.
403

404
!!! important
405
    Due to a peculiar behavior of the CMD parser/interpreter, each command
406
    after a literal `|` character (indicating a command pipeline) must have
407
    `shell_escape_wincmd` applied twice since it will be parsed twice by CMD.
408
    This implies ENV variables would also be expanded twice!
409
    For example:
410
    ```julia
411
    to_print = "All for 1 & 1 for all!"
412
    to_print_esc = Base.shell_escape_wincmd(Base.shell_escape_wincmd(to_print))
413
    run(Cmd(Cmd(["cmd", "/S /C \\" break | echo \$(to_print_esc) \\""]), windows_verbatim=true))
414
    ```
415

416
With an I/O stream parameter `io`, the result will be written there,
417
rather than returned as a string.
418

419
See also [`Base.escape_microsoft_c_args()`](@ref), [`Base.shell_escape_posixly()`](@ref).
420

421
# Examples
422
```jldoctest
423
julia> Base.shell_escape_wincmd("a^\\"^o\\"^u\\"")
424
"a^^\\"^o\\"^^u^\\""
425
```
426
"""
427
function shell_escape_wincmd(io::IO, s::AbstractString)
×
428
    # https://stackoverflow.com/a/4095133/1990689
429
    occursin(r"[\r\n\0]"sa, s) &&
×
430
        throw(ArgumentError("control character unsupported by CMD.EXE"))
431
    i = 1
×
432
    len = ncodeunits(s)
×
433
    if len > 0 && s[1] == '@'
×
434
        write(io, '^')
×
435
    end
436
    while i <= len
×
437
        c = s[i]
×
438
        if c == '"' && (j = findnext('"', s, nextind(s,i))) !== nothing
×
439
            write(io, SubString(s,i,j))
×
440
            i = j
×
441
        else
442
            if c in ('"', '(', ')', '!', '^', '<', '>', '&', '|')
×
443
                write(io, '^', c)
×
444
            else
445
                write(io, c)
×
446
            end
447
        end
448
        i = nextind(s,i)
×
449
    end
×
450
end
451
shell_escape_wincmd(s::AbstractString) = sprint(shell_escape_wincmd, s;
×
452
                                                sizehint = 2*sizeof(s))
453

454
"""
455
    escape_microsoft_c_args(args::Union{Cmd,AbstractString...})
456
    escape_microsoft_c_args(io::IO, args::Union{Cmd,AbstractString...})
457

458
Convert a collection of string arguments into a string that can be
459
passed to many Windows command-line applications.
460

461
Microsoft Windows passes the entire command line as a single string to
462
the application (unlike POSIX systems, where the shell splits the
463
command line into a list of arguments). Many Windows API applications
464
(including julia.exe), use the conventions of the [Microsoft C/C++
465
runtime](https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments)
466
to split that command line into a list of strings.
467

468
This function implements an inverse for a parser compatible with these rules.
469
It joins command-line arguments to be passed to a Windows
470
C/C++/Julia application into a command line, escaping or quoting the
471
meta characters space, TAB, double quote and backslash where needed.
472

473
See also [`Base.shell_escape_wincmd()`](@ref), [`Base.escape_raw_string()`](@ref).
474
"""
475
function escape_microsoft_c_args(io::IO, args::AbstractString...)
×
476
    # http://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
477
    first = true
×
478
    for arg in args
×
479
        if first
×
480
            first = false
×
481
        else
482
            write(io, ' ')  # separator
×
483
        end
484
        if isempty(arg) || occursin(r"[ \t\"]"sa, arg)
×
485
            # Julia raw strings happen to use the same escaping convention
486
            # as the argv[] parser in Microsoft's C runtime library.
487
            write(io, '"')
×
488
            escape_raw_string(io, arg)
×
489
            write(io, '"')
×
490
        else
491
            write(io, arg)
×
492
        end
493
    end
×
494
end
495
escape_microsoft_c_args(args::AbstractString...) =
×
496
    sprint(escape_microsoft_c_args, args...;
497
           sizehint = (sum(sizeof, args) + 3*length(args)))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc