From ef725f0be9340df05fc591d4672bd2cab309acad Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 10 Oct 2020 21:53:15 +0200 Subject: [PATCH 01/19] add redirect --- base/exports.jl | 1 + base/stream.jl | 86 +++++++++++++++++++++++++++++++++----- doc/src/base/io-network.md | 1 + test/spawn.jl | 19 +++++++++ 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index 2c0c628eec866..d29d26c801804 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -816,6 +816,7 @@ export readline, readlines, readuntil, + redirect, redirect_stderr, redirect_stdin, redirect_stdout, diff --git a/base/stream.jl b/base/stream.jl index 9ce58744b53f1..7bc5f1aee0127 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1215,20 +1215,78 @@ i.e. data to be read from [`stdin`](@ref) may be written to `wr`. """ redirect_stdin -for (F,S) in ((:redirect_stdin, :stdin), (:redirect_stdout, :stdout), (:redirect_stderr, :stderr)) - @eval function $F(f::Function, stream) - STDOLD = $S - local ret - $F(stream) - try - ret = f() - finally - $F(STDOLD) - end - ret + +""" + redirect(;stdin=stdin, stderr=stderr, stdout=stdout) + +Redirect a subset of the streams `stdin`, `stderr`, `stdout`. +""" +function redirect(;stdin=nothing, stderr=nothing, stdout=nothing) + stdin === nothing || redirect_stdin(stdin) + stderr === nothing || redirect_stderr(stderr) + stdout === nothing || redirect_stdout(stdout) +end + +""" + redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) + + +Redirect a subset of the streams `stdin`, `stderr`, `stdout`, +call `f()` and restore each stream. + +Possible values for each stream are: +* `nothing` indicating the stream should not be redirected. +* `path::AbstractString` redirecting the stream to the file at `path`. +* `io` an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. + +```julia +julia> redirect(stdout="stdout.txt", stderr="stderr.txt") do + print("hello stdout") + print(stderr, "hello stderr") + end + +julia> read("stdout.txt", String) +"hello stdout" + +julia> read("stderr.txt", String) +"hello stderr" +``` + +!!! compat "Julia 1.6" + `redirect` requires Julia 1.6 or later. +""" +function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) + + function resolve(new::Nothing, oldstream, mode) + (new=nothing, close=false, old=nothing) + end + function resolve(path::AbstractString, oldstream,mode) + (new=open(path, mode), close=true, old=oldstream) + end + function resolve(new, oldstream, mode) + (new=new, close=false, old=oldstream) + end + + new_err, close_err, old_err = resolve(stderr, Base.stderr, "w") + new_in , close_in , old_in = resolve(stdin , Base.stdin , "r") + new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") + + redirect(; stderr=new_err, stdin=new_in, stdout=new_out) + + try + return f() + finally + redirect(;stderr=old_err, stdin=old_in, stdout=old_out) + close_err && close(new_err) + close_in && close(new_in ) + close_out && close(new_out) end end +redirect_stderr(f::Function, stream) = redirect(f, stderr=stream) +redirect_stdin(f::Function, stream) = redirect(f, stdin=stream) +redirect_stdout(f::Function, stream) = redirect(f, stdout=stream) + """ redirect_stdout(f::Function, stream) @@ -1237,6 +1295,8 @@ Upon completion, [`stdout`](@ref) is restored to its prior setting. !!! note `stream` must be a `TTY`, a `Pipe`, or a socket. + +See also [`redirect`](@ref). """ redirect_stdout(f::Function, stream) @@ -1248,6 +1308,8 @@ Upon completion, [`stderr`](@ref) is restored to its prior setting. !!! note `stream` must be a `TTY`, a `Pipe`, or a socket. + +See also [`redirect`](@ref). """ redirect_stderr(f::Function, stream) @@ -1259,6 +1321,8 @@ Upon completion, [`stdin`](@ref) is restored to its prior setting. !!! note `stream` must be a `TTY`, a `Pipe`, or a socket. + +See also [`redirect`](@ref). """ redirect_stdin(f::Function, stream) diff --git a/doc/src/base/io-network.md b/doc/src/base/io-network.md index b798a708f22b2..5a99e16781b24 100644 --- a/doc/src/base/io-network.md +++ b/doc/src/base/io-network.md @@ -36,6 +36,7 @@ Base.iswritable Base.isreadable Base.isopen Base.fd +Base.redirect Base.redirect_stdout Base.redirect_stdout(::Function, ::Any) Base.redirect_stderr diff --git a/test/spawn.jl b/test/spawn.jl index 6028d7a9aa5fa..f8be00d8778aa 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -261,6 +261,25 @@ end end end +@testset "redirect" begin + mktempdir() do dir + cd(dir) do + content_stderr = randstring() + content_stdin = randstring() + content_stdout = randstring() + write("stdin.txt", content_stdin) + line = redirect(stdout="stdout.txt", stderr="stderr.txt", stdin="stdin.txt") do + print(content_stdout) + print(stderr, content_stderr) + readline() + end + @test read("stderr.txt", String) == content_stderr + @test line == content_stdin + @test read("stdout.txt", String) == content_stdout + end + end +end + # issue #36136 @testset "redirect to devnull" begin @test redirect_stdout(devnull) do; println("Hello") end === nothing From 8c97e4b724f5e138d138c3d110bdd44c036bef38 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 10 Oct 2020 23:16:42 +0200 Subject: [PATCH 02/19] fix --- test/spawn.jl | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/test/spawn.jl b/test/spawn.jl index f8be00d8778aa..58256765e8e83 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -263,20 +263,25 @@ end @testset "redirect" begin mktempdir() do dir - cd(dir) do - content_stderr = randstring() - content_stdin = randstring() - content_stdout = randstring() - write("stdin.txt", content_stdin) - line = redirect(stdout="stdout.txt", stderr="stderr.txt", stdin="stdin.txt") do - print(content_stdout) - print(stderr, content_stderr) - readline() - end - @test read("stderr.txt", String) == content_stderr - @test line == content_stdin - @test read("stdout.txt", String) == content_stdout + path_stderr = joinpath(dir, "stderr.txt") + path_stdin = joinpath(dir, "stdin.txt") + path_stdout = joinpath(dir, "stdout.txt") + + content_stderr = randstring() + content_stdin = randstring() + content_stdout = randstring() + + write(path_stdin, content_stdin) + + line = redirect(stdout=path_stdout, stderr=path_stderr, stdin=path_stdin) do + print(content_stdout) + print(stderr, content_stderr) + readline() end + + @test read(path_stderr, String) == content_stderr + @test line == content_stdin + @test read(path_stdout, String) == content_stdout end end From 4a027782536971eef9fb41f66fd98a7426387425 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 13 Oct 2020 10:49:24 +0200 Subject: [PATCH 03/19] fix redirect: better handling of duplicate paths --- base/stream.jl | 23 ++++++++++++++++++++--- test/spawn.jl | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/base/stream.jl b/base/stream.jl index 7bc5f1aee0127..7b80fa63643b0 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1267,9 +1267,26 @@ function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) (new=new, close=false, old=oldstream) end - new_err, close_err, old_err = resolve(stderr, Base.stderr, "w") - new_in , close_in , old_in = resolve(stdin , Base.stdin , "r") - new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") + same_path(x, y) = false + same_path(x::AbstractString, y::AbstractString) = x == y + if same_path(stderr, stdin) + throw(ArgumentError("stdin and stderr cannot be the same path")) + end + if same_path(stdout, stdin) + throw(ArgumentError("stdin and stdout cannot be the same path")) + end + + if same_path(stderr, stdout) + # make sure that in case stderr = stdout = "same/path" + # only a single io is used instead of opening the same file twice + new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") + new_err, close_err, old_err = new_out, false, Base.stderr + else + new_err, close_err, old_err = resolve(stderr, Base.stderr, "w") + new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") + end + + new_in, close_in, old_in = resolve(stdin , Base.stdin , "r") redirect(; stderr=new_err, stdin=new_in, stdout=new_out) diff --git a/test/spawn.jl b/test/spawn.jl index 58256765e8e83..863660e18bfaf 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -262,6 +262,38 @@ end end @testset "redirect" begin + + function hello_err_out() + println(stderr, "hello from stderr") + println(stdout, "hello from stdout") + end + @testset "same path for multiple streams" begin + @test_throws ArgumentError redirect(hello_err_out, + stdin="samepath.txt", stdout="samepath.txt") + @test_throws ArgumentError redirect(hello_err_out, + stdin="samepath.txt", stderr="samepath.txt") + mktempdir() do dir + path = joinpath(dir, "stdouterr.txt") + redirect(hello_err_out, stdout=path, stderr=path) + @test read(path, String) == """ + hello from stderr + hello from stdout + """ + end + end + + mktempdir() do dir + path_stdout = joinpath(dir, "stdout.txt") + path_stderr = joinpath(dir, "stderr.txt") + redirect(hello_err_out, stderr=devnull, stdout=path_stdout) + @test read(path_stdout, String) == "hello from stdout\n" + + open(path_stderr, "w") do ioerr + redirect(hello_err_out, stderr=ioerr, stdout=devnull) + end + @test read(path_stderr, String) == "hello from stderr\n" + end + mktempdir() do dir path_stderr = joinpath(dir, "stderr.txt") path_stdin = joinpath(dir, "stdin.txt") From 05a074ede993d0b62ee94d0563c97d73ed637b58 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 14 Oct 2020 00:12:28 +0200 Subject: [PATCH 04/19] fix redirect tests --- test/spawn.jl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/spawn.jl b/test/spawn.jl index 863660e18bfaf..6d848b53e0c4b 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -300,21 +300,29 @@ end path_stdout = joinpath(dir, "stdout.txt") content_stderr = randstring() - content_stdin = randstring() content_stdout = randstring() - write(path_stdin, content_stdin) - - line = redirect(stdout=path_stdout, stderr=path_stderr, stdin=path_stdin) do + redirect(stdout=path_stdout, stderr=path_stderr) do print(content_stdout) print(stderr, content_stderr) - readline() end @test read(path_stderr, String) == content_stderr - @test line == content_stdin @test read(path_stdout, String) == content_stdout end + + # stdin is unavailable on the workers. Run test on master. + ret = Core.eval(Main, + quote + remotecall_fetch(1) do + mktempdir() do dir + path = joinpath(dir, "stdin.txt") + write(path, "hello from stdin\n") + redirect(readline, stdin=path) + end + end + end) + @test ret == "hello from stdin" end # issue #36136 From 102e88f666f0eac8f026813da6507f38a9095296 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 08:31:16 +0200 Subject: [PATCH 05/19] add redirect to NEWS.md --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index edd474671f359..b90844cb82200 100644 --- a/NEWS.md +++ b/NEWS.md @@ -84,6 +84,7 @@ New library functions efficiently ([#35816]). * New function `addenv` for adding environment mappings into a `Cmd` object, returning the new `Cmd` object. * New function `insorted` for determining whether an element is in a sorted collection or not ([#37490]). +* New function `redirect` for redirecting `stdin`, `stdout` and `stderr` ([#37978]). New library features -------------------- From 689fba07575de68bd585353d009bb1b81d917fd9 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 08:51:35 +0200 Subject: [PATCH 06/19] Document redirect edge cases --- base/stream.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/base/stream.jl b/base/stream.jl index 7b80fa63643b0..d0a79072bce69 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1239,6 +1239,7 @@ Possible values for each stream are: * `path::AbstractString` redirecting the stream to the file at `path`. * `io` an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. +# Examples ```julia julia> redirect(stdout="stdout.txt", stderr="stderr.txt") do print("hello stdout") @@ -1252,6 +1253,27 @@ julia> read("stderr.txt", String) "hello stderr" ``` +# Edge cases + +It is possible to pass the same exactly same argument to `stdout` and `stderr`: +```julia +julia> redirect(stdout="log.txt", stderr="log.txt", stdin=devnull) do + ... +end +``` + +However it is not supported to pass two distinct descriptors of the same resource. +```julia +julia> io1 = open("same/path", "w") + +julia> io2 = open("same/path", "w") + +# This is not suppored +julia> redirect(stdout=io1, stderr=io2) do + ... +end +``` + !!! compat "Julia 1.6" `redirect` requires Julia 1.6 or later. """ From fc89b11a4c2a0e8953dd54bba6d9b9f4108c4ef2 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 10:04:54 +0200 Subject: [PATCH 07/19] improve redirect docs --- base/stream.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/base/stream.jl b/base/stream.jl index d0a79072bce69..21c77be02f927 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1262,16 +1262,19 @@ julia> redirect(stdout="log.txt", stderr="log.txt", stdin=devnull) do end ``` -However it is not supported to pass two distinct descriptors of the same resource. +However it is not supported to pass two distinct descriptors of the same file. ```julia julia> io1 = open("same/path", "w") julia> io2 = open("same/path", "w") -# This is not suppored -julia> redirect(stdout=io1, stderr=io2) do - ... -end +julia> redirect(f, stdout=io1, stderr=io2) # not suppored +``` +Also the `stdin` argument may not be the same descriptor as `stdout` or `stderr`. +```julia +julia> io = open(...) + +julia> redirect(f, stdout=io, stdin=io) # not supported ``` !!! compat "Julia 1.6" From 8ed4949c67729534d85cbffc841aca8a5c852b7b Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 22 Oct 2020 15:24:46 +0200 Subject: [PATCH 08/19] minor cosmetic change --- base/stream.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/base/stream.jl b/base/stream.jl index 21c77be02f927..33da89fa24809 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1301,18 +1301,16 @@ function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) throw(ArgumentError("stdin and stdout cannot be the same path")) end + new_in , close_in , old_in = resolve(stdin , Base.stdin , "r") + new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") if same_path(stderr, stdout) # make sure that in case stderr = stdout = "same/path" # only a single io is used instead of opening the same file twice - new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") new_err, close_err, old_err = new_out, false, Base.stderr else new_err, close_err, old_err = resolve(stderr, Base.stderr, "w") - new_out, close_out, old_out = resolve(stdout, Base.stdout, "w") end - new_in, close_in, old_in = resolve(stdin , Base.stdin , "r") - redirect(; stderr=new_err, stdin=new_in, stdout=new_out) try From 98917f19b0b00b95bf024c795105922cd3f6cdd1 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 24 Oct 2020 20:39:21 +0200 Subject: [PATCH 09/19] Update base/stream.jl Co-authored-by: Jeff Bezanson --- base/stream.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/stream.jl b/base/stream.jl index 33da89fa24809..0da35f5aed0c1 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1255,7 +1255,7 @@ julia> read("stderr.txt", String) # Edge cases -It is possible to pass the same exactly same argument to `stdout` and `stderr`: +It is possible to pass the same argument to `stdout` and `stderr`: ```julia julia> redirect(stdout="log.txt", stderr="log.txt", stdin=devnull) do ... From 5473a9018a2c273aff011af358294ec39d3a3f46 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 24 Oct 2020 20:44:58 +0200 Subject: [PATCH 10/19] Update stream.jl --- base/stream.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/stream.jl b/base/stream.jl index 0da35f5aed0c1..f61d2043ea7f8 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1220,6 +1220,7 @@ redirect_stdin redirect(;stdin=stdin, stderr=stderr, stdout=stdout) Redirect a subset of the streams `stdin`, `stderr`, `stdout`. +Each argument must be an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. """ function redirect(;stdin=nothing, stderr=nothing, stdout=nothing) stdin === nothing || redirect_stdin(stdin) @@ -1230,7 +1231,6 @@ end """ redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) - Redirect a subset of the streams `stdin`, `stderr`, `stdout`, call `f()` and restore each stream. From 0c3315ecce46c00aa6cdeff823c2145a2653e73a Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 23 Apr 2021 09:52:16 +0200 Subject: [PATCH 11/19] fix some docstrings --- base/stream.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/stream.jl b/base/stream.jl index 29894fb2048cf..452725c12cdeb 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1363,7 +1363,7 @@ end Run the function `f` while redirecting [`stdout`](@ref) to `stream`. Upon completion, [`stdout`](@ref) is restored to its prior setting. """ -redirect_stdout +redirect_stdout(f::Function, stream) """ redirect_stderr(f::Function, stream) @@ -1371,7 +1371,7 @@ redirect_stdout Run the function `f` while redirecting [`stderr`](@ref) to `stream`. Upon completion, [`stderr`](@ref) is restored to its prior setting. """ -redirect_stderr +redirect_stderr(f::Function, stream) """ redirect_stdin(f::Function, stream) @@ -1379,7 +1379,7 @@ redirect_stderr Run the function `f` while redirecting [`stdin`](@ref) to `stream`. Upon completion, [`stdin`](@ref) is restored to its prior setting. """ -redirect_stdin +redirect_stdin(f::Function, stream) mark(x::LibuvStream) = mark(x.buffer) unmark(x::LibuvStream) = unmark(x.buffer) From f213be6b0d9efad91087c78fc5a9bf337bc2cad3 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 23 Apr 2021 10:43:28 +0200 Subject: [PATCH 12/19] Update base/stream.jl Co-authored-by: Fredrik Ekre --- base/stream.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/stream.jl b/base/stream.jl index 452725c12cdeb..775775b46bec3 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1297,8 +1297,8 @@ julia> io = open(...) julia> redirect(f, stdout=io, stdin=io) # not supported ``` -!!! compat "Julia 1.6" - `redirect` requires Julia 1.6 or later. +!!! compat "Julia 1.7" + `redirect` requires Julia 1.7 or later. """ function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) From ff60deeb4fe74ca8178b1c00fb050086861082da Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 23 Apr 2021 10:45:28 +0200 Subject: [PATCH 13/19] add a compat note to redirect --- base/stream.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/stream.jl b/base/stream.jl index 775775b46bec3..b5ec4eae581b6 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1241,6 +1241,9 @@ redirect_stdin Redirect a subset of the streams `stdin`, `stderr`, `stdout`. Each argument must be an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. + +!!! compat "Julia 1.7" + `redirect` requires Julia 1.7 or later. """ function redirect(;stdin=nothing, stderr=nothing, stdout=nothing) stdin === nothing || redirect_stdin(stdin) From 137bd60ef6200a3ccae152cd80a9657c93d6f30a Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 13 May 2021 09:09:09 +0200 Subject: [PATCH 14/19] rename redirect -> redirect_stdio --- base/exports.jl | 2 +- base/stream.jl | 48 ++++++++++++++++++++++++------------------------ test/spawn.jl | 14 +++++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index f176e76a7c263..899e10a34801b 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -830,7 +830,7 @@ export readline, readlines, readuntil, - redirect, + redirect_stdio, redirect_stderr, redirect_stdin, redirect_stdout, diff --git a/base/stream.jl b/base/stream.jl index b5ec4eae581b6..552d2a7a2cfdd 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1127,7 +1127,7 @@ function _fd(x::Union{LibuvStream, LibuvServer}) return fd[] end -struct redirect_stdio <: Function +struct RedirectStdStream <: Function unix_fd::Int writable::Bool end @@ -1135,7 +1135,7 @@ for (f, writable, unix_fd) in ((:redirect_stdin, false, 0), (:redirect_stdout, true, 1), (:redirect_stderr, true, 2)) - @eval const ($f) = redirect_stdio($unix_fd, $writable) + @eval const ($f) = RedirectStdStream($unix_fd, $writable) end function _redirect_io_libc(stream, unix_fd::Int) posix_fd = _fd(stream) @@ -1154,7 +1154,7 @@ function _redirect_io_global(io, unix_fd::Int) unix_fd == 2 && (global stderr = io) nothing end -function (f::redirect_stdio)(handle::Union{LibuvStream, IOStream}) +function (f::RedirectStdStream)(handle::Union{LibuvStream, IOStream}) _redirect_io_libc(handle, f.unix_fd) c_sym = f.unix_fd == 0 ? cglobal(:jl_uv_stdin, Ptr{Cvoid}) : f.unix_fd == 1 ? cglobal(:jl_uv_stdout, Ptr{Cvoid}) : @@ -1164,7 +1164,7 @@ function (f::redirect_stdio)(handle::Union{LibuvStream, IOStream}) _redirect_io_global(handle, f.unix_fd) return handle end -function (f::redirect_stdio)(::DevNull) +function (f::RedirectStdStream)(::DevNull) nulldev = @static Sys.iswindows() ? "NUL" : "/dev/null" handle = open(nulldev, write=f.writable) _redirect_io_libc(handle, f.unix_fd) @@ -1172,13 +1172,13 @@ function (f::redirect_stdio)(::DevNull) _redirect_io_global(devnull, f.unix_fd) return devnull end -function (f::redirect_stdio)(io::AbstractPipe) +function (f::RedirectStdStream)(io::AbstractPipe) io2 = (f.writable ? pipe_writer : pipe_reader)(io) f(io2) _redirect_io_global(io, f.unix_fd) return io end -function (f::redirect_stdio)(p::Pipe) +function (f::RedirectStdStream)(p::Pipe) if p.in.status == StatusInit && p.out.status == StatusInit link_pipe!(p) end @@ -1186,9 +1186,9 @@ function (f::redirect_stdio)(p::Pipe) f(io2) return p end -(f::redirect_stdio)() = f(Pipe()) +(f::RedirectStdStream)() = f(Pipe()) -# Deprecate these in v2 (redirect_stdio support) +# Deprecate these in v2 (RedirectStdStream support) iterate(p::Pipe) = (p.out, 1) iterate(p::Pipe, i::Int) = i == 1 ? (p.in, 2) : nothing getindex(p::Pipe, key::Int) = key == 1 ? p.out : key == 2 ? p.in : throw(KeyError(key)) @@ -1205,7 +1205,7 @@ the pipe. `stream` must be a compatible objects, such as an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. -See also [`redirect`](@ref). +See also [`redirect_stdio`](@ref). """ redirect_stdout @@ -1218,7 +1218,7 @@ Like [`redirect_stdout`](@ref), but for [`stderr`](@ref). `stream` must be a compatible objects, such as an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. -See also [`redirect`](@ref). +See also [`redirect_stdio`](@ref). """ redirect_stderr @@ -1232,27 +1232,27 @@ Note that the direction of the stream is reversed. `stream` must be a compatible objects, such as an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. -See also [`redirect`](@ref). +See also [`redirect_stdio`](@ref). """ redirect_stdin """ - redirect(;stdin=stdin, stderr=stderr, stdout=stdout) + redirect_stdio(;stdin=stdin, stderr=stderr, stdout=stdout) Redirect a subset of the streams `stdin`, `stderr`, `stdout`. Each argument must be an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. !!! compat "Julia 1.7" - `redirect` requires Julia 1.7 or later. + `redirect_stdio` requires Julia 1.7 or later. """ -function redirect(;stdin=nothing, stderr=nothing, stdout=nothing) +function redirect_stdio(;stdin=nothing, stderr=nothing, stdout=nothing) stdin === nothing || redirect_stdin(stdin) stderr === nothing || redirect_stderr(stderr) stdout === nothing || redirect_stdout(stdout) end """ - redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) + redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing) Redirect a subset of the streams `stdin`, `stderr`, `stdout`, call `f()` and restore each stream. @@ -1264,7 +1264,7 @@ Possible values for each stream are: # Examples ```julia -julia> redirect(stdout="stdout.txt", stderr="stderr.txt") do +julia> redirect_stdio(stdout="stdout.txt", stderr="stderr.txt") do print("hello stdout") print(stderr, "hello stderr") end @@ -1280,7 +1280,7 @@ julia> read("stderr.txt", String) It is possible to pass the same argument to `stdout` and `stderr`: ```julia -julia> redirect(stdout="log.txt", stderr="log.txt", stdin=devnull) do +julia> redirect_stdio(stdout="log.txt", stderr="log.txt", stdin=devnull) do ... end ``` @@ -1291,19 +1291,19 @@ julia> io1 = open("same/path", "w") julia> io2 = open("same/path", "w") -julia> redirect(f, stdout=io1, stderr=io2) # not suppored +julia> redirect_stdio(f, stdout=io1, stderr=io2) # not suppored ``` Also the `stdin` argument may not be the same descriptor as `stdout` or `stderr`. ```julia julia> io = open(...) -julia> redirect(f, stdout=io, stdin=io) # not supported +julia> redirect_stdio(f, stdout=io, stdin=io) # not supported ``` !!! compat "Julia 1.7" - `redirect` requires Julia 1.7 or later. + `redirect_stdio` requires Julia 1.7 or later. """ -function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) +function redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing) function resolve(new::Nothing, oldstream, mode) (new=nothing, close=false, old=nothing) @@ -1334,19 +1334,19 @@ function redirect(f; stdin=nothing, stderr=nothing, stdout=nothing) new_err, close_err, old_err = resolve(stderr, Base.stderr, "w") end - redirect(; stderr=new_err, stdin=new_in, stdout=new_out) + redirect_stdio(; stderr=new_err, stdin=new_in, stdout=new_out) try return f() finally - redirect(;stderr=old_err, stdin=old_in, stdout=old_out) + redirect_stdio(;stderr=old_err, stdin=old_in, stdout=old_out) close_err && close(new_err) close_in && close(new_in ) close_out && close(new_out) end end -function (f::redirect_stdio)(thunk::Function, stream) +function (f::RedirectStdStream)(thunk::Function, stream) stdold = f.unix_fd == 0 ? stdin : f.unix_fd == 1 ? stdout : f.unix_fd == 2 ? stderr : diff --git a/test/spawn.jl b/test/spawn.jl index e0198ffd21f9c..823cebd55c6d2 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -261,20 +261,20 @@ end end end -@testset "redirect" begin +@testset "redirect_stdio" begin function hello_err_out() println(stderr, "hello from stderr") println(stdout, "hello from stdout") end @testset "same path for multiple streams" begin - @test_throws ArgumentError redirect(hello_err_out, + @test_throws ArgumentError redirect_stdio(hello_err_out, stdin="samepath.txt", stdout="samepath.txt") - @test_throws ArgumentError redirect(hello_err_out, + @test_throws ArgumentError redirect_stdio(hello_err_out, stdin="samepath.txt", stderr="samepath.txt") mktempdir() do dir path = joinpath(dir, "stdouterr.txt") - redirect(hello_err_out, stdout=path, stderr=path) + redirect_stdio(hello_err_out, stdout=path, stderr=path) @test read(path, String) == """ hello from stderr hello from stdout @@ -285,11 +285,11 @@ end mktempdir() do dir path_stdout = joinpath(dir, "stdout.txt") path_stderr = joinpath(dir, "stderr.txt") - redirect(hello_err_out, stderr=devnull, stdout=path_stdout) + redirect_stdio(hello_err_out, stderr=devnull, stdout=path_stdout) @test read(path_stdout, String) == "hello from stdout\n" open(path_stderr, "w") do ioerr - redirect(hello_err_out, stderr=ioerr, stdout=devnull) + redirect_stdio(hello_err_out, stderr=ioerr, stdout=devnull) end @test read(path_stderr, String) == "hello from stderr\n" end @@ -302,7 +302,7 @@ end content_stderr = randstring() content_stdout = randstring() - redirect(stdout=path_stdout, stderr=path_stderr) do + redirect_stdio(stdout=path_stdout, stderr=path_stderr) do print(content_stdout) print(stderr, content_stderr) end From 9a824128c04347d7d1e4b5d705fbb64e760a7fe3 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 13 May 2021 09:31:08 +0200 Subject: [PATCH 15/19] fix doc --- doc/src/base/io-network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/base/io-network.md b/doc/src/base/io-network.md index 5a99e16781b24..2d6a462400813 100644 --- a/doc/src/base/io-network.md +++ b/doc/src/base/io-network.md @@ -36,7 +36,7 @@ Base.iswritable Base.isreadable Base.isopen Base.fd -Base.redirect +Base.redirect_stdio Base.redirect_stdout Base.redirect_stdout(::Function, ::Any) Base.redirect_stderr From c204a357309dc9b84af417aa93acb27bcb1cb754 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 13 May 2021 09:36:59 +0200 Subject: [PATCH 16/19] fix redirect_stdio NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 65a0de865a25a..2e6a33bac6827 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,7 +46,7 @@ New library functions * New functor `Returns(value)`, which returns `value` for any arguments ([#39794]) * New macro `Base.@invoke f(arg1::T1, arg2::T2; kwargs...)` provides an easier syntax to call `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)` ([#38438]) * New macros `@something` and `@coalesce` which are short-circuiting versions of `something` and `coalesce`, respectively ([#40729]) -* New function `redirect` for redirecting `stdin`, `stdout` and `stderr` ([#37978]). +* New function `redirect_stdio` for redirecting `stdin`, `stdout` and `stderr` ([#37978]). New library features -------------------- From 96c6be6b2dcc502f75a267a7982fecf19f756329 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 13 May 2021 11:51:46 +0200 Subject: [PATCH 17/19] fix --- test/spawn.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spawn.jl b/test/spawn.jl index 823cebd55c6d2..2f26d634b18ac 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -318,7 +318,7 @@ end mktempdir() do dir path = joinpath(dir, "stdin.txt") write(path, "hello from stdin\n") - redirect(readline, stdin=path) + redirect_stdio(readline, stdin=path) end end end) From 1ab790470b79934a219f79298af743e268ab7b4f Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 25 May 2021 11:06:48 +0200 Subject: [PATCH 18/19] Update base/stream.jl Co-authored-by: Kristoffer Carlsson --- base/stream.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/stream.jl b/base/stream.jl index 552d2a7a2cfdd..0930d84225222 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1316,7 +1316,7 @@ function redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing) end same_path(x, y) = false - same_path(x::AbstractString, y::AbstractString) = x == y + same_path(x::AbstractString, y::AbstractString) = samefile(x, y) if same_path(stderr, stdin) throw(ArgumentError("stdin and stderr cannot be the same path")) end From c7d125f805c0a51f565a8500e2509e02b5f62070 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 25 May 2021 15:49:29 +0200 Subject: [PATCH 19/19] fix --- base/stream.jl | 5 ++++- test/spawn.jl | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/base/stream.jl b/base/stream.jl index acdab137f3e58..ddebb04eba7b3 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -1317,7 +1317,10 @@ function redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing) end same_path(x, y) = false - same_path(x::AbstractString, y::AbstractString) = samefile(x, y) + function same_path(x::AbstractString, y::AbstractString) + # if x = y = "does_not_yet_exist.txt" then samefile will return false + (abspath(x) == abspath(y)) || samefile(x,y) + end if same_path(stderr, stdin) throw(ArgumentError("stdin and stderr cannot be the same path")) end diff --git a/test/spawn.jl b/test/spawn.jl index 502b23e08aa36..d68d0c24436ad 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -272,6 +272,10 @@ end stdin="samepath.txt", stdout="samepath.txt") @test_throws ArgumentError redirect_stdio(hello_err_out, stdin="samepath.txt", stderr="samepath.txt") + + @test_throws ArgumentError redirect_stdio(hello_err_out, + stdin=joinpath("tricky", "..", "samepath.txt"), + stderr="samepath.txt") mktempdir() do dir path = joinpath(dir, "stdouterr.txt") redirect_stdio(hello_err_out, stdout=path, stderr=path)