web-dev-qa-db-fra.com

Comment exécuter une commande de terminal dans un script Swift? (Par exemple, xcodebuild))

Je souhaite remplacer mes scripts CI bash par Swift. Je n'arrive pas à comprendre comment appeler une commande de terminal normale telle que ls ou 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 ....
58
Robert

Si vous n'utilisez pas les sorties de commande dans Swift, ce qui suit serait suffisant:

#!/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")

Mise à jour: pour Swift3/Xcode8

106
rintaro

Si vous souhaitez utiliser les arguments de la ligne de commande "exactement" comme vous le feriez en ligne de commande (sans séparer tous les arguments), essayez ce qui suit.

(Cette réponse améliore la réponse de LegoLess et peut être utilisée dans 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")
40
user3064009

Le problème ici est que vous ne pouvez pas mélanger Bash et Swift. Vous savez déjà comment exécuter Swift en ligne de commande, vous devez maintenant ajouter les méthodes permettant d'exécuter les commandes Shell dans Swift. En résumé, à partir de PracticalSwift blog:

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
}

Le code Swift) suivant exécutera xcodebuild avec des arguments, puis affichera le résultat.

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

En ce qui concerne la recherche du contenu du répertoire (ce que ls fait dans Bash), je suggère d’utiliser NSFileManager et d’analyser le répertoire directement dans Swift, au lieu de la sortie Bash, ce qui peut être difficile à analyser .

30
Legoless

Fonction utilitaire In Swift 3.

Cela renvoie également l'état de fin des tâches et attend son achèvement.

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
Arun

Si vous souhaitez utiliser l'environnement bash pour appeler des commandes, utilisez la fonction bash suivante, qui utilise une version corrigée de Legoless. J'ai dû supprimer une nouvelle ligne du résultat de la fonction Shell.

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

Par exemple, pour obtenir la branche git active du répertoire de travail actuel:

let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")
16
Pellet

Script complet basé sur la réponse de 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
Robert

Mise à jour pour Swift 4.0 (traitant des modifications apportées à 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
rougeExciter

Juste pour mettre à jour ceci depuis Apple a déconseillé à la fois les .launchPath et launch (), voici une fonction utilitaire mise à jour pour Swift 4 qui devrait être un peu plus fiable .

Remarque: la documentation d'Apple sur les remplacements ( run () , executableURL , etc.) est pratiquement vide à ce stade.

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

Devrait pouvoir coller ceci directement dans un terrain de jeu pour le voir en action.

4
angusc

Petite amélioration avec le support des variables 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)
}
0

Mélanger les réponses de rintaro et de Legoless pour 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
richy

Exemple d'utilisation de la classe Process pour exécuter un script Python.

Également:

 - 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
Janusz Chudzynski