How to redirect stderr to stdout?

code

const std = @import("std");
const testing  = std.testing;

test "run cmd redirect stderr to stdout" {
    const allocator = testing.allocator;

    var p = std.process.Child.init(&[_][]const u8{ "zig", "-asdfak" }, allocator);
    p.stdout_behavior = .Pipe;
    p.stderr = p.stdout; // NOTE: not works: redirect stderrr to stdout

    try p.spawn();

    const stdout = try p.stdout.?.reader().readAllAlloc(allocator, 1024 * 64);
    defer allocator.free(stdout);

    const term = try p.wait();

    try testing.expectEqual(1, term.Exited);
    try testing.expect(std.mem.indexOf(u8, stdout, "error") != null);
}

It looks like stdout and stderr get assigned in the spawn process (for posix). You can see that here in: zig/lib/std/child_process.zig at master · ziglang/zig · GitHub

      // we are the parent
        const pid = @as(i32, @intCast(pid_result));
        if (self.stdin_behavior == StdIo.Pipe) {
            self.stdin = File{ .handle = stdin_pipe[1] };
        } else {
            self.stdin = null;
        }
        if (self.stdout_behavior == StdIo.Pipe) {
            self.stdout = File{ .handle = stdout_pipe[0] };
        } else {
            self.stdout = null;
        }
        if (self.stderr_behavior == StdIo.Pipe) {
            self.stderr = File{ .handle = stderr_pipe[0] };
        } else {
            self.stderr = null;
        }

So by the time you get here, it’s too late:

    const stdout = try p.stdout.?.reader().readAllAlloc(allocator, 1024 * 64);

I take it you don’t want to use run, collect the result and then print to standard out, no? You can redirect quite easily from there but I’m not seeing an obvious way using spawn.

Also, what os are we talking about? There’s two different spawn implementations there based on windows and posix.

    pub fn spawn(self: *ChildProcess) SpawnError!void {
        if (!std.process.can_spawn) {
            @compileError("the target operating system cannot spawn processes");
        }

        if (builtin.os.tag == .windows) {
            return self.spawnWindows();
        } else {
            return self.spawnPosix();
        }
    }

I test it on windows :wink:

A cursory look at the following source code implementation shows that the spawn function will reset stderr. So it’s impossible to implement the features I need :frowning:

On Windows, you can always hack the import address table and provide your own version of WriteFile.

For Posix platforms, you can override write() by expose a symbol called os in the root file:

pub const os = myOS;

The process is a bit of a pain right now, as we can’t create struct with decls using @Type(). To override that one function, you’ll need to declare a new struct and copy all other functions from std.os.linux or std.os.darwin.

Ouch. Bummer.

I’m going to go out on a limb here and say this behavior probably isn’t directly supported by the standard library at the moment.

@aiac, I can’t speak to the problem you are working on, but have you considered using run instead of spawning a child process and piping the results back? That may not solve your issue but you could take that returned buffer from the result of run and direct it wherever you want.

Should be possible to dup(2) stdin after closing stderr. Search something like “how to spawn process fork exec c” if you’d like some examples.

1 Like