From ef725f0be9340df05fc591d4672bd2cab309acad Mon Sep 17 00:00:00 2001
From: Jan Weidner <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
Date: Sat, 24 Oct 2020 20:39:21 +0200
Subject: [PATCH 09/19] Update base/stream.jl

Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com>
---
 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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
Date: Fri, 23 Apr 2021 10:43:28 +0200
Subject: [PATCH 12/19] Update base/stream.jl

Co-authored-by: Fredrik Ekre <ekrefredrik@gmail.com>
---
 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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
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 <jw3126@gmail.com>
Date: Tue, 25 May 2021 11:06:48 +0200
Subject: [PATCH 18/19] Update base/stream.jl

Co-authored-by: Kristoffer Carlsson <kcarlsson89@gmail.com>
---
 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 <jw3126@gmail.com>
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)