I'm trying to redirect stdout to a string with Swift, to collect whatever is passed to the print function. I've read through a few resources that suggest my approach should be working, however, the following proof of concept script only outputs "Starting":
import Foundation
let outPipe = Pipe()
var outString = "Initial"
outPipe.fileHandleForReading.readabilityHandler = { fileHandle in
outString += String(data: fileHandle.availableData, encoding: .utf8)!
}
print("Starting")
// Redirect
setvbuf(stdout, nil, _IONBF, 0)
dup2(outPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
print("Captured")
freopen("/dev/stdout", "a", stdout)
print(outString)
Any ideas where I've gone wrong? All help is appreciated!
The main problem is that the pipe's read handler is called on a secondary queue, but your program does not have a run loop. Therefore the program terminates before the event handler is called once.
In a command line application, one option so solve this is with a “dispatch semaphore” which is signaled in the event handler, once it detect an end-of-file condition on the pipe. And in order to make this work, we must close the writing end of the pipe eventually.
Also the standard output file descriptor should be saved and restored later. Reopening "/dev/stdout" will not work if the program is run in the Xcode debugger console.
The following code produces the expected output. It is meant as a “proof of concept” example. In your production code you'll want to avoid things like a forced try!.
import Foundation
let outPipe = Pipe()
var outString = "Initial"
let sema = DispatchSemaphore(value: 0)
outPipe.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if data.isEmpty { // end-of-file condition
fileHandle.readabilityHandler = nil
sema.signal()
} else {
outString += String(data: data, encoding: .utf8)!
}
}
print("Starting")
// Redirect
setvbuf(stdout, nil, _IONBF, 0)
let savedStdout = dup(STDOUT_FILENO)
dup2(outPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
print("Captured")
// Undo redirection
dup2(savedStdout, STDOUT_FILENO)
try! outPipe.fileHandleForWriting.close()
close(savedStdout)
sema.wait() // Wait until read handler is done
print(outString) // Prints "InitialCaptured"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With