Как я выполняю терминальную команду в быстром сценарии? (например, xcodebuild)

Я хочу заменить свои сценарии удара CI быстро. Я не могу выяснить, как вызвать нормальную терминальную команду такой как 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 ....
62
задан 2 October 2018 в 12:29

11 ответов

Если бы Вы не используете выводы команды в 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

113
ответ дан 31 October 2019 в 13:04

Если требуется использовать параметры командной строки "точно", как Вы были бы в командной строке (не разделяя все аргументы), попробовать следующее.

(Этот ответ улучшается прочь ответа LegoLess и может использоваться в Swift 4 Xcode 9.3)

func shell(_ command: String) -> String {
    let task = Process()
    task.launchPath = "/bin/bash"
    task.arguments = ["-c", command]

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

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

    return output
}

// Example usage:
shell("ls -la")
45
ответ дан 31 October 2019 в 13:04

Проблема здесь состоит в том, что Вы не можете Bash смешивания и подгонки и Swift. Вы уже знаете, как запустить скрипт Swift из командной строки, теперь необходимо добавить методы для выполнения команд Shell в Swift. Таким образом, от блог 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-код выполнит xcodebuild с аргументами и затем произведет результат.

shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);

Что касается поиска содержания каталога (который является тем, что ls делает в Bash), я предлагаю использовать NSFileManager и просканировать каталог непосредственно в Swift вместо вывода Bash, который может быть болью для парсинга.

30
ответ дан 31 October 2019 в 13:04

Служебная функция В Быстром 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)
}
20
ответ дан 31 October 2019 в 13:04

Если требуется использовать среду удара для вызова использования команд следующая функция удара, которая использует согласованную версию Legoless. Я должен был удалить запаздывающую новую строку из результата функции оболочки.

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)
}

, Например, для получения текущего рабочего ответвления мерзавца текущего рабочего каталога:

let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")
16
ответ дан 31 October 2019 в 13:04

Полный сценарий на основе ответа Legoless

#!/usr/bin/env xcrun swift

import Foundation

func printShell(launchPath: String, arguments: [AnyObject] = []) {
    let output = shell(launchPath, arguments:arguments)

    if (output != nil) {
        println(output!)
    }
}

func shell(launchPath: String, arguments: [AnyObject] = []) -> String? {

    let task = NSTask()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = NSPipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output: String? = NSString(data: data, encoding: NSUTF8StringEncoding)

    return output
}

// > ls
// > ls -a -g
printShell("/bin/ls")
printShell("/bin/ls", arguments:["-a", "-g"])
9
ответ дан 31 October 2019 в 13:04

Обновление для Быстрых 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)
}
7
ответ дан 31 October 2019 в 13:04

Только для обновления этого, так как Apple удержала от использования и .launchPath и запуск () вот обновленная служебная функция для Swift 4, который должен немного более соответствовать требованиям завтрашнего дня.

Примечание: документация Apple относительно замен ( выполненный () , executableURL, и т.д.) в основном пуста в этой точке.

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")

Должен смочь вставить это непосредственно в детскую площадку для наблюдения его в действии.

6
ответ дан 31 October 2019 в 13:04

Смешивание rintaro и ответы Legoless для Swift 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
}
0
ответ дан 31 October 2019 в 13:04

Маленькое улучшение с поддержкой огибающих переменных:

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)
}
0
ответ дан 31 October 2019 в 13:04

Пример использования класса Процесса для запущения скрипта 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)")
   }
}
0
ответ дан 31 October 2019 в 13:04

Другие вопросы по тегам:

Похожие вопросы: