Skip to content

Instantly share code, notes, and snippets.

@andreacipriani
Last active February 15, 2024 12:50
Show Gist options
  • Save andreacipriani/8c3af3719da31c8fae2cdfa8c21e17ba to your computer and use it in GitHub Desktop.
Save andreacipriani/8c3af3719da31c8fae2cdfa8c21e17ba to your computer and use it in GitHub Desktop.
Execute shell/bash commands from Swift
import UIKit
protocol CommandExecuting {
func run(commandName: String, arguments: [String]) throws -> String
}
enum BashError: Error {
case commandNotFound(name: String)
}
struct Bash: CommandExecuting {
func run(commandName: String, arguments: [String] = []) throws -> String {
return try run(resolve(commandName), with: arguments)
}
private func resolve(_ command: String) throws -> String {
guard var bashCommand = try? run("/bin/bash" , with: ["-l", "-c", "which \(command)"]) else {
throw BashError.commandNotFound(name: command)
}
bashCommand = bashCommand.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
return bashCommand
}
private func run(_ command: String, with arguments: [String] = []) throws -> String {
let process = Process()
process.launchPath = command
process.arguments = arguments
let outputPipe = Pipe()
process.standardOutput = outputPipe
process.launch()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
return output
}
}
let bash: CommandExecuting = Bash()
if let lsOutput = try? bash.run(commandName: "ls", arguments: []) { print(lsOutput) }
if let lsWithArgumentsOutput = try? bash.run(commandName: "ls", arguments: ["-la"]) { print(lsWithArgumentsOutput) }
@gary17
Copy link

gary17 commented Sep 30, 2020

Also, perhaps it would be beneficial to factor out which apart from run to allow a consumer to decide what exactly to execute:: https://gist.github.com/gary17/b6c8b662da36cefee7010cd4bdcb2ee6

@moritzluedtke
Copy link

This works great, thank you for the gist!

I do get an error when using the command, although the given command is still executed. I get /bin/bash: /etc/profile: Operation not permitted before every print out of my original command.

Example code:

let cli = Bash()
if let output = try? cli.run(commandName: "pwd") {
    print(output)
}

I did change the import from UiKit to AppKit since I'm developing for MacOS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment