I've been trying for some days now to find and equivalent to Ruby's exec
in Swift.
I haven't yet been able to find a solution. Multiple sources online suggest how to forward predefined input to STDIN of the child process and out to read its output, but now how to start a child process and let it take control of the STDIN and STDOUT as if the Swift process was letting it take over.
The use case would be to run SSH commands and develop an in-house company tool, formerly in Ruby, that would allow similar features as heroku run /bin/sh
for instance. I am already able to replicate things like heroku logs
, since they donnot require user input.
The basic idea of starting a new process and attaching it to STDIN and STDOUT doesn't seem to be working as expected.
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = ["bash", "-c", "read"]
task.standardInput = FileHandle.standardInput
task.standardOutput = FileHandle.standardOutput
task.standardError = FileHandle.standardError
task.launch()
task.waitUntilExit()
Any ideas or hints of how to achieve this ?
CodePudding user response:
This appears to be a bug in Apple's Foundation. The intended behaviour should be that not assigning any of those would get you your desired result. As the documentation says:
If this method isn’t used, the standard input is inherited from the process that created the receiver.
(Similar things are said for stdout
and stderr
too)
However, stdin
doesn't seem to be actually connected, so your input is being received by your Swift process, rather than bash -c read
.
According to this similar discussion here on Swift Forums about allowing users to enter passwords into a subprocess,
Process
is a wrapper aroundposix_spawn
, but callingposix_spawn
directly works- Rust's
Command
and Python'ssubprocess.run
also useposix_spawn
, but they work correctly. - Building the
SwiftFoundation
framework from source and using theProcess
class from that, also works.
Based on the above points, I think this is a bug.
For now, you can use this workaround (this is also from the Swift Forums post)
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", "read"]
let pipe = Pipe()
task.standardInput = pipe
let fileHandle = FileHandle(fileDescriptor: STDIN_FILENO)
fileHandle.readabilityHandler = { handle in
let data = handle.availableData
if data.count > 0 {
pipe.fileHandleForWriting.write(data)
}
}
try task.run()
task.waitUntilExit()