Zev Eisenberg
Zev Eisenberg

Reputation: 8158

Chaining shell commands with pipe in a Swift script

I'm trying to chain shell command together in a Swift script. The actual commands in question are the output of gource piped to the input of ffmpeg, but here's a simplified, contrived example of what I'm trying to do:

let echo = Process()

echo.launchPath = "/usr/bin/env"
echo.arguments = ["echo", "foo\nbar\nbaz\nbaz", "|", "uniq"]

let pipe = Pipe()
echo.standardOutput = pipe

echo.launch()
echo.waitUntilExit()

// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)

print(output ?? "no output")

Expected output:

foo
bar
baz

Actual output:

foo
bar
baz
baz | uniq

The | is being interpreted as part of the last argument. How can I chain commands together, so that data flows from one to the next, in a Swift script? I tried various combinations of assigning standardIn and standardOut and using Pipe() between two Processes, but either I'm doing it wrong, or I'm not connecting the right pieces together.

Upvotes: 5

Views: 2896

Answers (3)

The Pulsing Eye
The Pulsing Eye

Reputation: 161

You should take a look a this. It's an example of an easy way to pipe by chaining. https://gist.github.com/eahrold/b5c5fd455225a8726e1cc31708e139db

so you can something like

 let ls = Process("/bin/ls", ["-al"])
 let grep = Process("/usr/bin/grep", ["com"])
 let cut = Process("/usr/bin/awk", ["{print $3}"])

 ls.pipe(grep).pipe(cut).complete {
    message, status in
    print("Your results: \(message)")
 }
 ls.launch()

Upvotes: -1

David T
David T

Reputation: 2764

For others that may be running into the same problem, here's what I derived from Zev's answer to fit with creating a SHA1 HMAC hash:

func getHMAC(forValue value: String, key: String) -> String {

    let pipe = Pipe()

    let echo = Process()
    echo.launchPath = "/usr/bin/env"
    echo.arguments = ["echo", "-n", value]
    echo.standardOutput = pipe

    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = ["openssl", "sha1", "-hmac", key]
    task.standardInput = pipe

    let output = Pipe()
    task.standardOutput = output

    echo.launch()
    task.launch()
    task.waitUntilExit()

    let data = output.fileHandleForReading.readDataToEndOfFile()
    return NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
}

This is especially useful when making simple API calls from Swift scripts which require a hash in the HTTP header. As far as I found, this was the only way it was possible (since we cannot make use of the Objective-C CommonCrypto framework in Swift scripts)

Upvotes: 0

Zev Eisenberg
Zev Eisenberg

Reputation: 8158

I got an answer with help from zadr:

import Foundation

let pipe = Pipe()

let echo = Process()
echo.launchPath = "/usr/bin/env"
echo.arguments = ["echo", "foo\nbar\nbaz\nbaz"]
echo.standardOutput = pipe

let uniq = Process()
uniq.launchPath = "/usr/bin/env"
uniq.arguments = ["uniq"]
uniq.standardInput = pipe

let out = Pipe()
uniq.standardOutput = out

echo.launch()
uniq.launch()
uniq.waitUntilExit()

let data = out.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)

print(output ?? "no output")

Upvotes: 12

Related Questions