Swift 스크립트에서 터미널 명령을 실행하려면 어떻게 해야 합니까?(예: xcodebuild)입니다.
CI bash 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로 로'하다'와 같은 정상적인 단말 명령어를 어떻게 호출해야 하는지 알 수가 .ls
아니면요?xcodebuild
#!/usr/bin/env xcrun swift
import Foundation // Works
println("Test") // Works
ls // Fails
xcodebuild -workspace myApp.xcworkspace // Fails
$ ./script.swift
./script.swift:5:1: error: use of unresolved identifier 'ls'
ls // Fails
^
... etc ....
명령줄 인수를 모든 인수를 분리하지 않고 "정확하게" 사용하려면 다음을 수행하십시오.
(이 답변은 LegoLess의 답변보다 개선되었으며 Swift 5에서 사용할 수 있습니다.)
import Foundation
func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.standardInput = nil
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
// Example usage:
shell("ls -la")
업데이트됨/안전한 함수 호출 10/23/21: 위의 셸 명령을 사용하여 런타임 오류가 발생할 수 있습니다. 발생할 경우 아래의 업데이트된 호출로 전환해 보십시오.새 셸 명령어 주변에 do catch 문을 사용해야 하지만, 이렇게 하면 예기치 않은 오류를 탐지하는 방법을 찾는 데 시간이 절약될 것입니다.
설명:task.launch()는 던지기 함수가 아니기 때문에 잡을 수 없고, 호출 시 앱이 크래시 되는 경우가 종종 있었습니다.인터넷 검색을 많이 한 결과, 앱이 충돌할 때 제대로 오류를 발생시키는 새로운 함수 task.run()을 위해 프로세스 클래스가 사용되지 않는 task.launch()를 발견했습니다.업데이트된 방법에 대한 자세한 내용은 https://eclecticlight.co/2019/02/02/scripting-in-swift-process-deprecations/를 참조하십시오.
import Foundation
@discardableResult // Add to suppress warnings when you don't want/need a result
func safeShell(_ command: String) throws -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.executableURL = URL(fileURLWithPath: "/bin/zsh") //<--updated
task.standardInput = nil
try task.run() //<--updated
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
예를 들어 다음과 같습니다.
// Example usage capturing error:
do {
try safeShell("ls -la")
}
catch {
print("\(error)") //handle or silence the error here
}
// Example usage where you don't care about the error and want a nil back instead
let result = try? safeShell("ls -la")
// Example usage where you don't care about the error or the return value
try? safeShell("ls -la")
참고: 사용 중인 마지막 경우입니다.try?
이 결과를 사용하지 않고 있습니다. 어떤 이유에서인지 컴파일러가 경고하고 있습니다. 어떤 이유로 컴파일러는 여전히 경고합니다.@discardableResult
. 이런 일은 하다 하다에서만 일어납니다try?
이 아니라요try
A 안에 있습니다.do-try-catch
블록 또는 던지기 기능 내에서 차단합니다.이겁니다.
Swift 코드에서 명령 출력을 사용하지 않는 경우 다음과 같이 충분합니다.
#!/usr/bin/env swift
import Foundation
@discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
shell("ls")
shell("xcodebuild", "-workspace", "myApp.xcworkspace")
업데이트됨: Swift3/Xcode8용입니다.
문제는 Bash와 Swift를 혼재시킬 수 없다는 것입니다.명령줄에서 Swift 스크립트를 실행하는 방법을 이미 알고 있으므로 이제 Swift에서 Shell 명령을 실행하는 방법을 추가해야 합니다.PracticalSwift 블로그에서 요약하면 다음과 같습니다.
func shell(_ launchPath: String, _ arguments: [String]) -> String?
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: String.Encoding.utf8)
return output
}
Swift 코드는 Swift를 실행합니다.xcodebuild
이겁니다.
shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
디렉토리 내용(즉, 디렉토리 내용 검색)의 경우입니다.ls
)를 사용하는 것이 좋습니다.NSFileManager
Bash 출력 대신 Swift에서 직접 디렉토리를 스캔하면 구문 분석하기가 힘들 수 있습니다.
Swift 3.0의 유틸리티 기능입니다.
또한 작업 종료 상태를 반환하고 완료될 때까지 기다립니다.
func shell(launchPath: String, arguments: [String] = []) -> (String? , Int32) {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (output, task.terminationStatus)
}
호출 명령에 bash 환경을 사용하려면 고정 버전의 Legoless를 사용하는 다음 bash 함수를 사용하십시오.셸 함수의 결과에서 후행하는 줄바꿈을 제거해야 했습니다.
Swift 3.0:(Xcode8)입니다.
import Foundation
func shell(launchPath: String, arguments: [String]) -> String
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: String.Encoding.utf8)!
if output.characters.count > 0 {
//remove newline character.
let lastIndex = output.index(before: output.endIndex)
return output[output.startIndex ..< lastIndex]
}
return output
}
func bash(command: String, arguments: [String]) -> String {
let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ])
return shell(launchPath: whichPathForCommand, arguments: arguments)
}
예를 들어, 현재 작업 디렉토리의 현재 작업 git 분기를 가져오려면 다음과 같이 하십시오.
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")
Apple이 .launchPath와 launch()를 모두 추천하지 않았기 때문에 이를 업데이트하기 위해 Swift 4에 대한 업데이트된 유틸리티 기능이 있습니다.
참고: 교체(실행()), 실행 파일에 대한 Apple의 설명서입니다.URL 등)은 기본적으로 비어 있습니다.
import Foundation
// wrapper function for shell commands
// must provide full path to executable
func shell(_ launchPath: String, _ arguments: [String] = []) -> (String?, Int32) {
let task = Process()
task.executableURL = URL(fileURLWithPath: launchPath)
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
do {
try task.run()
} catch {
// handle errors
print("Error: \(error.localizedDescription)")
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (output, task.terminationStatus)
}
// valid directory listing test
let (goodOutput, goodStatus) = shell("/bin/ls", ["-la"])
if let out = goodOutput { print("\(out)") }
print("Returned \(goodStatus)\n")
// invalid test
let (badOutput, badStatus) = shell("ls")
놀이터에 직접 붙일 수 있어야만 움직이는 것을 볼 수 있습니다.
전체 대본은 레골레스의 답변을 기반으로 합니다.
#!/usr/bin/env swift
import Foundation
func printShell(launchPath: String, arguments: [String] = []) {
let output = shell(launchPath: launchPath, arguments: arguments)
if (output != nil) {
print(output!)
}
}
func shell(launchPath: String, arguments: [String] = []) -> String? {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: String.Encoding.utf8)
return output
}
// > ls
// > ls -a -g
printShell(launchPath: "/bin/ls")
printShell(launchPath: "/bin/ls", arguments:["-a", "-g"])
Swift 4.0용으로 업데이트 중입니다(변경 사항 포함).String
)
func shell(launchPath: String, arguments: [String]) -> String
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: String.Encoding.utf8)!
if output.count > 0 {
//remove newline character.
let lastIndex = output.index(before: output.endIndex)
return String(output[output.startIndex ..< lastIndex])
}
return output
}
func bash(command: String, arguments: [String]) -> String {
let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ])
return shell(launchPath: whichPathForCommand, arguments: arguments)
}
여기에 게시된 몇 가지 솔루션을 사용해 본 결과, 명령을 실행하는 가장 좋은 방법은-c
인수에 대한 플래그입니다.
@discardableResult func shell(_ command: String) -> (String?, Int32) {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (output, task.terminationStatus)
}
let _ = shell("mkdir ~/Desktop/test")
import Foundation
enum Commands {
struct Result {
public let statusCode: Int32
public let output: String
}
static func run(_ command: String,
environment: [String: String]? = nil,
executableURL: String = "/bin/bash",
dashc: String = "-c") -> Result {
// create process
func create(_ executableURL: String,
dashc: String,
environment: [String: String]?) -> Process {
let process = Process()
if #available(macOS 10.13, *) {
process.executableURL = URL(fileURLWithPath: executableURL)
} else {
process.launchPath = "/bin/bash"
}
if let environment = environment {
process.environment = environment
}
process.arguments = [dashc, command]
return process
}
// run process
func run(_ process: Process) throws {
if #available(macOS 10.13, *) {
try process.run()
} else {
process.launch()
}
process.waitUntilExit()
}
// read data
func fileHandleData(fileHandle: FileHandle) throws -> String? {
var outputData: Data?
if #available(macOS 10.15.4, *) {
outputData = try fileHandle.readToEnd()
} else {
outputData = fileHandle.readDataToEndOfFile()
}
if let outputData = outputData {
return String(data: outputData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
}
return nil
}
let process = create(executableURL, dashc: dashc, environment: environment)
let outputPipe = Pipe()
process.standardOutput = outputPipe
let errorPipe = Pipe()
process.standardError = errorPipe
do {
try run(process)
let outputActual = try fileHandleData(fileHandle: outputPipe.fileHandleForReading) ?? ""
let errorActual = try fileHandleData(fileHandle: errorPipe.fileHandleForReading) ?? ""
if process.terminationStatus == EXIT_SUCCESS {
return Result(statusCode: process.terminationStatus, output: outputActual)
}
return Result(statusCode: process.terminationStatus, output: errorActual)
} catch let error {
return Result(statusCode: process.terminationStatus, output: error.localizedDescription)
}
}
}
사용.
let result = Commands.run("ls")
debugPrint(result.output)
debugPrint(result.statusCode)
아니면 속전속결로요.
import Commands
Commands.Bash.system("ls")
스위프트 3에 대한 린타로와 레골리스의 답변을 섞습니다.
@discardableResult
func shell(_ args: String...) -> String {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output: String = String(data: data, encoding: .utf8) else {
return ""
}
return output
}
다음과 같이 env 변수를 지원하여 소폭 개선되었습니다.
func shell(launchPath: String,
arguments: [String] = [],
environment: [String : String]? = nil) -> (String , Int32) {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
if let environment = environment {
task.environment = environment
}
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8) ?? ""
task.waitUntilExit()
return (output, task.terminationStatus)
}
프로세스 클래스를 사용하여 Python 스크립트를 실행하는 예제입니다.
또한 다음을 수행합니다.
- added basic exception handling
- setting environment variables (in my case I had to do it to get Google SDK to authenticate correctly)
- arguments
import Cocoa
func shellTask(_ url: URL, arguments:[String], environment:[String : String]) throws ->(String?, String?){
let task = Process()
task.executableURL = url
task.arguments = arguments
task.environment = environment
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
try task.run()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
let error = String(decoding: errorData, as: UTF8.self)
return (output,error)
}
func pythonUploadTask()
{
let url = URL(fileURLWithPath: "/usr/bin/python")
let pythonScript = "upload.py"
let fileToUpload = "/CuteCat.mp4"
let arguments = [pythonScript,fileToUpload]
var environment = ProcessInfo.processInfo.environment
environment["PATH"]="usr/local/bin"
environment["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/j.chudzynski/GoogleCredentials/credentials.json"
do {
let result = try shellTask(url, arguments: arguments, environment: environment)
if let output = result.0
{
print(output)
}
if let output = result.1
{
print(output)
}
} catch {
print("Unexpected error:\(error)")
}
}
스위프트를 만들었어요Exec은 이러한 명령을 실행하기 위한 작은 라이브러리입니다.
import SwiftExec
var result: ExecResult
do {
result = try exec(program: "/usr/bin/git", arguments: ["status"])
} catch {
let error = error as! ExecError
result = error.execResult
}
print(result.exitCode!)
print(result.stdout!)
print(result.stderr!)
프로젝트에 쉽게 복사 붙여넣거나 SPM을 사용하여 설치할 수 있는 단일 파일 라이브러리입니다.테스트되고 오류 처리를 단순화합니다.
또한 다양한 사전 정의된 명령을 추가로 지원하는 ShellOut도 있습니다.
다음과 같은 터미널 명령을 실행하는 앱을 많이 보았습니다.
cd /Applications/Theirappname.app/Contents/Resources && do sth here
이 명령은 셸 스크립트를 실행하는 것과 다르지 않으며 앱이 Applications 폴더에 없으면 다음 오류가 발생하므로 올바르게 실행되지 않습니다.No such file or directory: /Applications/Theirappname.app
따라서 리소스 폴더에서 실행 파일을 실행하려면 다음 코드를 사용해야 합니다.
func runExec() -> Int32 {
let task = Process()
task.arguments = [Bundle.main.url(forResource: "YourExecutablefile", withExtension: "its_extension", subdirectory: "if_exists/")!.path]
//If it does not have an extension then you just leave it empty
//You can remove subdirectory if it does not exist
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
실행 파일에 인수가 필요한 경우 코드는 다음과 같습니다.
func runExec() -> Int32 {
let task = Process()
task.launchPath = "/bin/bash"
task.launchPath = Bundle.main.url(forResource: "YourExecutablefile", withExtension: "its_extension", subdirectory: "if_exists")?.path
//If it does not have an extension then you just leave it empty
//You can remove subdirectory if it does not exist
task.arguments = ["arg1","arg2"]
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
NSTask를 Swift로 사용한 기존 Objective-C 코드를 다시 팩터링하는 중이고, 다른 답변에서 누락된 한 가지 핵심은 대량의 stdout/stderr 출력을 어떻게 처리해야 하는가입니다.이렇게 하지 않으면 실행 프로세스에서 중단이 발생할 수 있습니다.
일반적으로 실행하는 명령 중 하나는 stdout과 stderr 모두에 대해 수백KB의 출력을 생성할 수 있습니다.
이 문제를 해결하기 위해 다음과 같이 출력을 버퍼링합니다.
import Foundation
struct ShellScriptExecutor {
static func runScript(_ script: ShellScript) -> ShellScriptResult {
var errors: String = ""
let tempFile = copyToTempFile(script)
let process = Process()
let stdout = Pipe()
let stderr = Pipe()
var stdoutData = Data.init(capacity: 8192)
var stderrData = Data.init(capacity: 8192)
process.standardOutput = stdout
process.standardError = stderr
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = [tempFile]
do {
try process.run()
// Buffer the data while running
while process.isRunning {
stdoutData.append(pipeToData(stdout))
stderrData.append(pipeToData(stderr))
}
process.waitUntilExit()
stdoutData.append(pipeToData(stdout))
errors = dataToString(stderrData) + pipeToString(stderr)
}
catch {
print("Process failed for " + tempFile + ": " + error.localizedDescription)
}
// Clean up
if !tempFile.isEmpty {
do {
try FileManager.default.removeItem(atPath: tempFile)
}
catch {
print("Unable to remove " + tempFile + ": " + error.localizedDescription)
}
}
return ShellScriptResult(stdoutData, script.resultType, errors)
}
static private func copyToTempFile(_ script: ShellScript) -> String {
let tempFile: String = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString + ".sh", isDirectory: false).path
if FileManager.default.createFile(atPath: tempFile, contents: Data(script.script.utf8), attributes: nil) {
return tempFile;
}
else {
return ""
}
}
static private func pipeToString(_ pipe: Pipe) -> String {
return dataToString(pipeToData(pipe))
}
static private func dataToString(_ data: Data) -> String {
return String(decoding: data, as: UTF8.self)
}
static private func pipeToData(_ pipe: Pipe) -> Data {
return pipe.fileHandleForReading.readDataToEndOfFile()
}
}
(ShellScript 및 ShellScriptResult는 단순한 래퍼 클래스일 뿐입니다.)
언급URL : https://stackoverflow.com/questions/26971240/how-do-i-run-a-terminal-command-in-a-swift-script-e-g-xcodebuild 입니다.
'programing' 카테고리의 다른 글
바이트를 int로 변환하시겠습니까? (0) | 2023.05.05 |
---|---|
명령이 실패하면 어떻게 종료합니까? (0) | 2023.04.25 |
네트워크 드라이브의 UNC 경로를 찾으시겠습니까? (0) | 2023.04.25 |
기본 64 문자열을 이미지로 변환하고 저장합니다. (0) | 2023.04.25 |
UIButton에 여러 줄 텍스트를 추가하려면 어떻게 해야 합니까? (0) | 2023.04.25 |