diff --git a/Sources/ShellOut.swift b/Sources/ShellOut.swift index 290b9d0..ab69462 100644 --- a/Sources/ShellOut.swift +++ b/Sources/ShellOut.swift @@ -15,6 +15,8 @@ import Dispatch * - parameter command: The command to run * - parameter arguments: The arguments to pass to the command * - parameter path: The path to execute the commands at (defaults to current folder) + * - parameter inputHandle: Any `FileHandle` that any input (STDIN) should be redirected to + * (at the moment this is only supported on macOS) * - parameter outputHandle: Any `FileHandle` that any output (STDOUT) should be redirected to * (at the moment this is only supported on macOS) * - parameter errorHandle: Any `FileHandle` that any error output (STDERR) should be redirected to @@ -29,11 +31,12 @@ import Dispatch @discardableResult public func shellOut(to command: String, arguments: [String] = [], at path: String = ".", + inputHandle: FileHandle? = nil, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String { let process = Process() let command = "cd \(path.escapingSpaces) && \(command) \(arguments.joined(separator: " "))" - return try process.launchBash(with: command, outputHandle: outputHandle, errorHandle: errorHandle) + return try process.launchBash(with: command, inputHandle: inputHandle, outputHandle: outputHandle, errorHandle: errorHandle) } /** @@ -41,6 +44,8 @@ import Dispatch * * - parameter commands: The commands to run * - parameter path: The path to execute the commands at (defaults to current folder) + * - parameter inputHandle: Any `FileHandle` that any input (STDIN) should be redirected to + * (at the moment this is only supported on macOS) * - parameter outputHandle: Any `FileHandle` that any output (STDOUT) should be redirected to * (at the moment this is only supported on macOS) * - parameter errorHandle: Any `FileHandle` that any error output (STDERR) should be redirected to @@ -54,10 +59,11 @@ import Dispatch */ @discardableResult public func shellOut(to commands: [String], at path: String = ".", + inputHandle: FileHandle? = nil, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String { let command = commands.joined(separator: " && ") - return try shellOut(to: command, at: path, outputHandle: outputHandle, errorHandle: errorHandle) + return try shellOut(to: command, at: path, inputHandle: inputHandle, outputHandle: outputHandle, errorHandle: errorHandle) } /** @@ -65,6 +71,7 @@ import Dispatch * * - parameter command: The command to run * - parameter path: The path to execute the commands at (defaults to current folder) + * - parameter inputHandle: Any `FileHandle` that any input (STDIN) should be redirected to * - parameter outputHandle: Any `FileHandle` that any output (STDOUT) should be redirected to * - parameter errorHandle: Any `FileHandle` that any error output (STDERR) should be redirected to * @@ -78,9 +85,10 @@ import Dispatch */ @discardableResult public func shellOut(to command: ShellOutCommand, at path: String = ".", + inputHandle: FileHandle? = nil, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String { - return try shellOut(to: command.string, at: path, outputHandle: outputHandle, errorHandle: errorHandle) + return try shellOut(to: command.string, at: path, inputHandle: inputHandle, outputHandle: outputHandle, errorHandle: errorHandle) } /// Structure used to pre-define commands for use with ShellOut @@ -346,7 +354,7 @@ extension ShellOutError: LocalizedError { // MARK: - Private private extension Process { - @discardableResult func launchBash(with command: String, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String { + @discardableResult func launchBash(with command: String, inputHandle: FileHandle? = nil, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String { launchPath = "/bin/bash" arguments = ["-c", command] @@ -359,12 +367,15 @@ private extension Process { var outputData = Data() var errorData = Data() + standardInput = inputHandle + let outputPipe = Pipe() standardOutput = outputPipe let errorPipe = Pipe() standardError = errorPipe + #if !os(Linux) outputPipe.fileHandleForReading.readabilityHandler = { handler in outputQueue.async { diff --git a/Tests/ShellOutTests/ShellOutTests.swift b/Tests/ShellOutTests/ShellOutTests.swift index 90a8fd3..fc5b39f 100644 --- a/Tests/ShellOutTests/ShellOutTests.swift +++ b/Tests/ShellOutTests/ShellOutTests.swift @@ -13,6 +13,17 @@ class ShellOutTests: XCTestCase { XCTAssertTrue(uptime.contains("load average")) } + func testWithInputData() throws { + let pipe = Pipe() + + // echo "Hello world" + let _ = try shellOut(to: "echo", arguments: ["Hello world"], outputHandle: pipe.fileHandleForWriting) + + // simulate `echo "Hello world" | cat` + let cat = try shellOut(to: "cat", inputHandle: pipe.fileHandleForReading) + XCTAssertEqual(cat, "Hello world") + } + func testWithArguments() throws { let echo = try shellOut(to: "echo", arguments: ["Hello world"]) XCTAssertEqual(echo, "Hello world")