Suppress print output with redirection

I'm using the code shown below to suppress print output by redirecting it to dev/null. Is there a more Swift-friendly approach to accomplish this?

import Foundation

func sayHello(to: String) {
    print("Hello", to)
}

func main() {

    let originalStdout = dup(fileno(stdout)) // Save original stdout
    let devNull = fopen("/dev/null", "w")    // Open /dev/null
    dup2(fileno(devNull), fileno(stdout))    // Redirect stdout to /dev/null

    sayHello(to: "Homer")  // This will not print

    fflush(stdout)                           // Flush output buffer
    dup2(originalStdout, fileno(stdout))     // Restore stdout
    close(originalStdout)                    // Close duplicate file descriptor
    fclose(devNull)                          // Close file stream

    sayHello(to: "Marge")  // This will print
}

main()
1 Like

The System framework (Apple platforms only) and Swift System package (cross-platform) feel more "Swifty" than the bridged C constructs.

1 Like

For the System framework/package, what is the equivalent to dup, dup2, and fflush?

The corresponding method to dup and dup2 is duplicate(as:retryOnInterrupt:).

System has no equivalent to fflush. In fact, it doesn't have any equivalent to the FILE * C stream APIs β€” only the file descriptor APIs. If you want to flush the contents of stdout's buffer, then you'll still need to use fflush. If you'd like, you can write a wrapper function that looks more idiomatic for Swift.

I don't see how FileDescriptor.duplicate can be used instead of dup and dup2. My attempt is shown below but it doesn't work because I can't get a rawValue from the duplicate method.

import Foundation
import System

func sayHello(to: String) {
    print("Hello", to)
}

func suppressPrint(_ block: () -> ()) {
    do {
        let stdoutFD = FileDescriptor.standardOutput.rawValue
        let savedFD = FileDescriptor.duplicate(.standardOutput)
        let nullFD = try! FileDescriptor.open("/dev/null", .writeOnly).rawValue
        dup2(nullFD, stdoutFD)

        block()

        fflush(stdout)
        dup2(savedFD, stdoutFD)
        close(savedFD)
        close(nullFD)
    } catch {
        print("Suppression failed:", error)
    }
}

func main() {
    suppressPrint {
        sayHello(to: "Homer")
    }

    sayHello(to: "Marge")
}

main()

FileDescriptor.duplicate is an instance method, meaning it’s intended to be used on an instance of FileDescriptor, not the type itself.

Try this code:

func suppressingPrint(_ body: () -> ()) {
    do {
        do {
            defer {
                body()
            }

            let savedFD = try FileDescriptor.standardOutput.duplicate()
            let nullFD = try FileDescriptor.open("/dev/null", .writeOnly)
            _ = try nullFD.duplicate(as: .standardOutput)
        }

        fflush(stdout)
        _ = try savedFD.duplicate(as: .standardOutput)
        try savedFD.close()
        try nullFD.close()
    } catch {
        print("Suppression failed:", error)
    }
}

Edit: updated the code so that the body parameter is always run, even if an error occurs.

Your example doesn't work because savedFD and nullFD can't be accessed outside the inner do statement. But it works fine if you get rid of the inner do statement as shown here:

import Foundation
import System

func suppressPrint(_ block: () -> Void) {
    do {
        let stdoutFD = try FileDescriptor.standardOutput.duplicate()
        let nullFD = try FileDescriptor.open("/dev/null", .writeOnly)
        _ = try nullFD.duplicate(as: .standardOutput)

        block()

        fflush(stdout)
        _ = try stdoutFD.duplicate(as: .standardOutput)
        try stdoutFD.close()
        try nullFD.close()
    } catch {
        print("Suppression failed:", error)
    }
}

You're right, savedFD and nullFD should be declared outside of the do statement. Be careful if you get rid of the do statement, however. Without it, if an error was thrown, you don't know if the body function was run or not.

1 Like