-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathProcess+LaunchBash.swift
More file actions
95 lines (78 loc) · 2.41 KB
/
Process+LaunchBash.swift
File metadata and controls
95 lines (78 loc) · 2.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//
// Process+LaunchBash.swift
// ShellKit
//
// Created by Lukas Romsicki on 2022-11-22.
// Copyright © 2022 Shopify. All rights reserved.
//
// Based on the implementation of ShellOut:
// https://github.com/JohnSundell/ShellOut/blob/d3db50b62a86b18f88f5de38cae5f3d80617d555/Sources/ShellOut.swift
//
import Foundation
public typealias StandardOutputHandler = @Sendable (String) -> Void
public typealias StandardErrorHandler = @Sendable (String) -> Void
extension Process {
@discardableResult func launchBash(
command: String,
timeout: TimeInterval? = nil,
standardOutputHandler: StandardOutputHandler? = nil,
standardErrorHandler: StandardErrorHandler? = nil
) throws -> String {
launchPath = "/bin/bash"
arguments = ["-c", command]
// Because FileHandle's readabilityHandler might be called from a
// different queue from the calling queue, avoid a data race by
// protecting reads and writes to outputData and errorData on
// a single dispatch queue.
let outputQueue = DispatchQueue(label: "bash-output-queue")
// Concurrency is protected using the DispatchQueue.
nonisolated(unsafe) var outputData = Data()
nonisolated(unsafe) var errorData = Data()
let outputPipe = Pipe()
standardOutput = outputPipe
let errorPipe = Pipe()
standardError = errorPipe
outputPipe.fileHandleForReading.readabilityHandler = { handler in
let data = handler.availableData
outputQueue.async {
outputData.append(data)
if !data.isEmpty {
standardOutputHandler?(data.utf8String)
}
}
}
errorPipe.fileHandleForReading.readabilityHandler = { handler in
let data = handler.availableData
outputQueue.async {
errorData.append(data)
if !data.isEmpty {
standardErrorHandler?(data.utf8String)
}
}
}
launch()
if let timeout {
let process = self
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
if process.isRunning {
process.terminate()
}
}
}
waitUntilExit()
outputPipe.fileHandleForReading.readabilityHandler = nil
errorPipe.fileHandleForReading.readabilityHandler = nil
// Block until all writes have occurred to outputData and errorData,
// and then read the data back out.
return try outputQueue.sync {
if terminationStatus != 0 {
throw ShellError(
terminationStatus: terminationStatus,
errorData: errorData,
outputData: outputData
)
}
return outputData.utf8String
}
}
}