@@ -1127,15 +1127,15 @@ function _fd(x::Union{LibuvStream, LibuvServer})
11271127 return fd[]
11281128end
11291129
1130- struct redirect_stdio <: Function
1130+ struct RedirectStdStream <: Function
11311131 unix_fd:: Int
11321132 writable:: Bool
11331133end
11341134for (f, writable, unix_fd) in
11351135 ((:redirect_stdin , false , 0 ),
11361136 (:redirect_stdout , true , 1 ),
11371137 (:redirect_stderr , true , 2 ))
1138- @eval const ($ f) = redirect_stdio ($ unix_fd, $ writable)
1138+ @eval const ($ f) = RedirectStdStream ($ unix_fd, $ writable)
11391139end
11401140function _redirect_io_libc (stream, unix_fd:: Int )
11411141 posix_fd = _fd (stream)
@@ -1154,7 +1154,7 @@ function _redirect_io_global(io, unix_fd::Int)
11541154 unix_fd == 2 && (global stderr = io)
11551155 nothing
11561156end
1157- function (f:: redirect_stdio )(handle:: Union{LibuvStream, IOStream} )
1157+ function (f:: RedirectStdStream )(handle:: Union{LibuvStream, IOStream} )
11581158 _redirect_io_libc (handle, f. unix_fd)
11591159 c_sym = f. unix_fd == 0 ? cglobal (:jl_uv_stdin , Ptr{Cvoid}) :
11601160 f. unix_fd == 1 ? cglobal (:jl_uv_stdout , Ptr{Cvoid}) :
@@ -1164,31 +1164,31 @@ function (f::redirect_stdio)(handle::Union{LibuvStream, IOStream})
11641164 _redirect_io_global (handle, f. unix_fd)
11651165 return handle
11661166end
1167- function (f:: redirect_stdio )(:: DevNull )
1167+ function (f:: RedirectStdStream )(:: DevNull )
11681168 nulldev = @static Sys. iswindows () ? " NUL" : " /dev/null"
11691169 handle = open (nulldev, write= f. writable)
11701170 _redirect_io_libc (handle, f. unix_fd)
11711171 close (handle) # handle has been dup'ed in _redirect_io_libc
11721172 _redirect_io_global (devnull , f. unix_fd)
11731173 return devnull
11741174end
1175- function (f:: redirect_stdio )(io:: AbstractPipe )
1175+ function (f:: RedirectStdStream )(io:: AbstractPipe )
11761176 io2 = (f. writable ? pipe_writer : pipe_reader)(io)
11771177 f (io2)
11781178 _redirect_io_global (io, f. unix_fd)
11791179 return io
11801180end
1181- function (f:: redirect_stdio )(p:: Pipe )
1181+ function (f:: RedirectStdStream )(p:: Pipe )
11821182 if p. in. status == StatusInit && p. out. status == StatusInit
11831183 link_pipe! (p)
11841184 end
11851185 io2 = getfield (p, f. writable ? :in : :out )
11861186 f (io2)
11871187 return p
11881188end
1189- (f:: redirect_stdio )() = f (Pipe ())
1189+ (f:: RedirectStdStream )() = f (Pipe ())
11901190
1191- # Deprecate these in v2 (redirect_stdio support)
1191+ # Deprecate these in v2 (RedirectStdStream support)
11921192iterate (p:: Pipe ) = (p. out, 1 )
11931193iterate (p:: Pipe , i:: Int ) = i == 1 ? (p. in, 2 ) : nothing
11941194getindex (p:: Pipe , key:: Int ) = key == 1 ? p. out : key == 2 ? p. in : throw (KeyError (key))
@@ -1204,6 +1204,8 @@ the pipe.
12041204!!! note
12051205 `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
12061206 `Pipe`, socket, or `devnull`.
1207+
1208+ See also [`redirect_stdio`](@ref).
12071209"""
12081210redirect_stdout
12091211
@@ -1215,6 +1217,8 @@ Like [`redirect_stdout`](@ref), but for [`stderr`](@ref).
12151217!!! note
12161218 `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
12171219 `Pipe`, socket, or `devnull`.
1220+
1221+ See also [`redirect_stdio`](@ref).
12181222"""
12191223redirect_stderr
12201224
@@ -1227,10 +1231,125 @@ Note that the direction of the stream is reversed.
12271231!!! note
12281232 `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
12291233 `Pipe`, socket, or `devnull`.
1234+
1235+ See also [`redirect_stdio`](@ref).
12301236"""
12311237redirect_stdin
12321238
1233- function (f:: redirect_stdio )(thunk:: Function , stream)
1239+ """
1240+ redirect_stdio(;stdin=stdin, stderr=stderr, stdout=stdout)
1241+
1242+ Redirect a subset of the streams `stdin`, `stderr`, `stdout`.
1243+ Each argument must be an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`.
1244+
1245+ !!! compat "Julia 1.7"
1246+ `redirect_stdio` requires Julia 1.7 or later.
1247+ """
1248+ function redirect_stdio (;stdin = nothing , stderr = nothing , stdout = nothing )
1249+ stdin === nothing || redirect_stdin (stdin )
1250+ stderr === nothing || redirect_stderr (stderr )
1251+ stdout === nothing || redirect_stdout (stdout )
1252+ end
1253+
1254+ """
1255+ redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing)
1256+
1257+ Redirect a subset of the streams `stdin`, `stderr`, `stdout`,
1258+ call `f()` and restore each stream.
1259+
1260+ Possible values for each stream are:
1261+ * `nothing` indicating the stream should not be redirected.
1262+ * `path::AbstractString` redirecting the stream to the file at `path`.
1263+ * `io` an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`.
1264+
1265+ # Examples
1266+ ```julia
1267+ julia> redirect_stdio(stdout="stdout.txt", stderr="stderr.txt") do
1268+ print("hello stdout")
1269+ print(stderr, "hello stderr")
1270+ end
1271+
1272+ julia> read("stdout.txt", String)
1273+ "hello stdout"
1274+
1275+ julia> read("stderr.txt", String)
1276+ "hello stderr"
1277+ ```
1278+
1279+ # Edge cases
1280+
1281+ It is possible to pass the same argument to `stdout` and `stderr`:
1282+ ```julia
1283+ julia> redirect_stdio(stdout="log.txt", stderr="log.txt", stdin=devnull) do
1284+ ...
1285+ end
1286+ ```
1287+
1288+ However it is not supported to pass two distinct descriptors of the same file.
1289+ ```julia
1290+ julia> io1 = open("same/path", "w")
1291+
1292+ julia> io2 = open("same/path", "w")
1293+
1294+ julia> redirect_stdio(f, stdout=io1, stderr=io2) # not suppored
1295+ ```
1296+ Also the `stdin` argument may not be the same descriptor as `stdout` or `stderr`.
1297+ ```julia
1298+ julia> io = open(...)
1299+
1300+ julia> redirect_stdio(f, stdout=io, stdin=io) # not supported
1301+ ```
1302+
1303+ !!! compat "Julia 1.7"
1304+ `redirect_stdio` requires Julia 1.7 or later.
1305+ """
1306+ function redirect_stdio (f; stdin = nothing , stderr = nothing , stdout = nothing )
1307+
1308+ function resolve (new:: Nothing , oldstream, mode)
1309+ (new= nothing , close= false , old= nothing )
1310+ end
1311+ function resolve (path:: AbstractString , oldstream,mode)
1312+ (new= open (path, mode), close= true , old= oldstream)
1313+ end
1314+ function resolve (new, oldstream, mode)
1315+ (new= new, close= false , old= oldstream)
1316+ end
1317+
1318+ same_path (x, y) = false
1319+ function same_path (x:: AbstractString , y:: AbstractString )
1320+ # if x = y = "does_not_yet_exist.txt" then samefile will return false
1321+ (abspath (x) == abspath (y)) || samefile (x,y)
1322+ end
1323+ if same_path (stderr , stdin )
1324+ throw (ArgumentError (" stdin and stderr cannot be the same path" ))
1325+ end
1326+ if same_path (stdout , stdin )
1327+ throw (ArgumentError (" stdin and stdout cannot be the same path" ))
1328+ end
1329+
1330+ new_in , close_in , old_in = resolve (stdin , Base. stdin , " r" )
1331+ new_out, close_out, old_out = resolve (stdout , Base. stdout , " w" )
1332+ if same_path (stderr , stdout )
1333+ # make sure that in case stderr = stdout = "same/path"
1334+ # only a single io is used instead of opening the same file twice
1335+ new_err, close_err, old_err = new_out, false , Base. stderr
1336+ else
1337+ new_err, close_err, old_err = resolve (stderr , Base. stderr , " w" )
1338+ end
1339+
1340+ redirect_stdio (; stderr = new_err, stdin = new_in, stdout = new_out)
1341+
1342+ try
1343+ return f ()
1344+ finally
1345+ redirect_stdio (;stderr = old_err, stdin = old_in, stdout = old_out)
1346+ close_err && close (new_err)
1347+ close_in && close (new_in )
1348+ close_out && close (new_out)
1349+ end
1350+ end
1351+
1352+ function (f:: RedirectStdStream )(thunk:: Function , stream)
12341353 stdold = f. unix_fd == 0 ? stdin :
12351354 f. unix_fd == 1 ? stdout :
12361355 f. unix_fd == 2 ? stderr :
@@ -1243,6 +1362,7 @@ function (f::redirect_stdio)(thunk::Function, stream)
12431362 end
12441363end
12451364
1365+
12461366"""
12471367 redirect_stdout(f::Function, stream)
12481368
0 commit comments