Bir Swift komut dosyasında bir terminal komutunu nasıl çalıştırırım? (örneğin xcodebuild)


91

CI bash komut dosyalarımı swift ile değiştirmek istiyorum. lsVeya gibi normal terminal komutunu nasıl çağıracağımı çözemiyorumxcodebuild

#!/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 ....

Yanıtlar:


138

Swift kodunda komut çıktılarını kullanmıyorsanız, aşağıdakiler yeterli olacaktır:

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

Güncellendi: Swift3 / Xcode8 için


3
'NSTask', 'Process' olarak yeniden adlandırıldı
Mateusz

4
Process () hala Swift 4'te mi? Tanımlanmamış bir sembol alıyorum. : /
Arnaldo Capo

1
@ArnaldoCapo Hala benim için iyi çalışıyor! İşte bir örnek:#!/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")
CorPruijs


5
İşlem yalnızca
macOS'ta

94

Komut satırı bağımsız değişkenlerini komut satırında yaptığınız gibi "tam olarak" kullanmak istiyorsanız (tüm bağımsız değişkenleri ayırmadan), aşağıdakileri deneyin.

(Bu cevap, LegoLess'in cevabını geliştirir ve Swift 5'te kullanılabilir)

import Foundation

func shell(_ command: String) -> String {
    let task = Process()
    let pipe = Pipe()
    
    task.standardOutput = pipe
    task.arguments = ["-c", command]
    task.launchPath = "/bin/zsh"
    task.launch()
    
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8)!
    
    return output
}

// Example usage:
shell("ls -la")

7
Bu cevap, öncekilerin birçok sorununu çözdüğü için gerçekten çok daha yüksek olmalıdır.
Steven Hepting

1
+1. Bu osx kullanıcılar için unutulmamalıdır /bin/bashifade eder bash-3.2. /usr/bin/env bash
Bash'in

Bu konuda kimse yardımcı olabilir mi? Bağımsız değişkenler stackoverflow.com/questions/62203978/…
mahdi

34

Buradaki sorun, Bash ve Swift'i karıştırıp eşleştirememenizdir. Swift komut dosyasının komut satırından nasıl çalıştırılacağını zaten biliyorsunuz, şimdi Swift'de Shell komutlarını çalıştırmak için yöntemler eklemeniz gerekiyor. PracticalSwift blogundan özet olarak :

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
}

Aşağıdaki Swift kodu, xcodebuildbağımsız değişkenlerle çalıştırılacak ve ardından sonucu çıkaracaktır .

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

Dizin içeriğini aramakla ilgili olarak ( lsBash'de ne yapar), NSFileManagerdizini Bash çıktısı yerine doğrudan Swift'de kullanmanızı ve taramanızı öneririm , bu da ayrıştırılması zor olabilir.


1
Harika - Bu derlemeyi yapmak için birkaç düzenleme yaptım, ancak çağırmaya çalışırken bir çalışma zamanı istisnası alıyorum shell("ls", [])- 'NSInvalidArgumentException', reason: 'launch path not accessible' Herhangi bir fikir?
Robert

5
NSTask vermez arama kabuk yaptığı gibi (ortamdan PATH kullanarak) yürütülebilir. Başlatma yolu, mutlak bir yol (ör. "/ Bin / ls") veya geçerli çalışma dizinine göre bir yol olmalıdır.
Martin R

stackoverflow.com/questions/386783/… PATH temelde bir kabuk kavramıdır ve erişilemez.
Legoless

Harika - şimdi çalışıyor. Tam komut dosyasını + eksiksizlik için birkaç değişiklik gönderdim. Teşekkür ederim.
Robert

2
Kabuk ("cd", "~ / Masaüstü /") kullanarak şunu elde ederim: / usr / bin / cd: satır 4: cd: ~ / Masaüstü /: Böyle bir dosya veya dizin yok
Zaporozhchenko Oleksandr

23

Swift 3.0'da yardımcı program işlevi

Bu aynı zamanda görev sonlandırma durumunu döndürür ve tamamlanmasını bekler.

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

5
import Foundationeksik
Binarian

3
Ne yazık ki iOS için değil.
Raphael

17

Komutları çağırmak için bash ortamını kullanmak isterseniz, Legoless'in sabitlenmiş bir sürümünü kullanan aşağıdaki bash işlevini kullanın. Kabuk işlevinin sonucundan sonraki satırsonu satırını kaldırmak zorunda kaldım.

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

Örneğin, geçerli çalışma dizininin geçerli çalışan git dalını almak için:

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

13

Legoless'in cevabına dayalı tam senaryo

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

11

Apple hem .launchPath hem de launch () 'yi kullanımdan kaldırdığı için bunu güncellemek için, işte Swift 4 için biraz daha ileriye dönük olması gereken güncellenmiş bir yardımcı program işlevi.

Not: Apple'ın değiştirmelerle ilgili belgeleri ( run () , yürütülebilirURL , vb.) Bu noktada temelde boştur.

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

Eylem halinde görmek için bunu doğrudan bir oyun alanına yapıştırabilmelidir.


8

Swift 4.0 için güncelleme (değişikliklerle başa çıkmak 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)
}

örnek ver
Gowtham Sooryaraj

4

Burada yayınlanan çözümlerden bazılarını denedikten sonra, komutları yürütmenin en iyi yolunun -cargümanlar için bayrağı kullanmak olduğunu buldum .

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

0

Swift 3 için rintaro ve Legoless'in cevapları karıştırılıyor

@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

Env değişkenleri desteğiyle küçük iyileştirme:

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

Bir Python betiğini çalıştırmak için Process sınıfını kullanma örneği.

Ayrıca:

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

"upload.py" dosyasını nereye yerleştirirsiniz
Suhaib Roomy

0

Bu tür komutları çalıştırmak için küçük bir kitaplık olan SwiftExec'i oluşturdum :

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

Projelere kolayca kopyalanıp yapıştırılabilen veya SPM kullanılarak yüklenebilen tek dosyalık bir kitaplıktır. Test edilmiş ve hata işlemeyi basitleştirmiştir.

Ayrıca çeşitli önceden tanımlanmış komutları da destekleyen ShellOut vardır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.