Swiftスクリプトでターミナルコマンドを実行するにはどうすればよいですか?(例:xcodebuild)


91

CIbashスクリプトをswiftに置き換えたい。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 ....

回答:


138

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用


3
「NSTask」は「Process」に名前が変更されました
Mateusz

4
Process()はまだSwift 4にありますか?未定義のシンボルが表示されます。:/
Arnaldo Capo

1
@ArnaldoCapoそれでも私にとっては問題なく動作します!次に例を示します:#!/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

2
私が得たものを試した:私が得たものを試した:i.imgur.com/Ge1OOCG.png
cyber82 0018

5
プロセスは、MacOSの上で使用可能です
shallowThought

94

コマンドラインで使用するのと同じように(すべての引数を分離せずに)コマンドライン引数を「正確に」使用する場合は、次のことを試してください。

(この回答はLegoLessの回答を改善し、Swift 5で使用できます)

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
この答えは、前の問題の多くを解決するので、実際にははるかに高いはずです。
Steven Hepting 2018

1
+1。を/bin/bash参照するosxユーザーには注意が必要ですbash-3.2。あなたはbashののより高度な機能を使用したい場合は、(パスを変更/usr/bin/env bash通常は良い選択肢である)
Aserre

誰でもこれを手伝うことができますか?引数は合格しないstackoverflow.com/questions/62203978/...を
マハディ

34

ここでの問題は、BashとSwiftを組み合わせて使用​​できないことです。コマンドラインからSwiftスクリプトを実行する方法はすでに知っているので、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"]);

ディレクトリの内容の検索(lsBashで行うこと)については、NSFileManager解析が面倒なBash出力ではなく、Swiftで直接ディレクトリを使用してスキャンすることをお勧めします。


1
すばらしい-このコンパイルを行うためにいくつかの編集を行いましたが、呼び出そうとするとランタイム例外が発生しますshell("ls", [])-'NSInvalidArgumentException', reason: 'launch path not accessible' 何かアイデアはありますか?
ロバート

5
NSTaskは、シェルのように(環境からのPATHを使用して)実行可能ファイルを検索しません。起動パスは、絶対パス( "/ bin / ls"など)または現在の作業ディレクトリからの相対パスである必要があります。
マーティンR

stackoverflow.com/questions/386783/…PATHは基本的にシェルの概念であり、到達できません。
レゴレス2014年

すばらしい-今は動作します。完全なスクリプトと完全を期すためのいくつかの変更を投稿しました。ありがとうございました。
ロバート

2
shell( "cd"、 "〜/ Desktop /")を使用すると、次のようになります。/usr / bin / cd:4行目:cd:〜/ Desktop /:そのようなファイルまたはディレクトリはありません
Zaporozhchenko Oleksandr

23

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

5
import Foundation行方不明
バイナリアン2016年

3
残念ながら、iOS用ではありません。
ラファエル

17

コマンドの呼び出しに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)")

13

レゴレスの答えに基づく完全なスクリプト

#!/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が.launchPathとlaunch()の両方を廃止したため、これを更新するために、Swift 4の更新されたユーティリティ関数を次に示します。これは、もう少し将来の証拠になるはずです。

注:置換(run()executableURLなど)に関するAppleのドキュメントは、この時点では基本的に空です。

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

これを直接遊び場に貼り付けて、実際の動作を確認できるはずです。


8

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

例を挙げてください
GowthamSooryaraj18年

4

ここに掲載されている解決策のいくつかを試した後、コマンドを実行する最良の方法-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")

0

Swift3のりんたろうとレゴレスの答えを混ぜる

@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

環境変数のサポートによる小さな改善:

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

Processクラスを使用して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)")
   }
}

ファイル「upload.py」をどこに配置しますか
SuhaibRoomy19年

0

このようなコマンドを実行するための小さなライブラリであるSwiftExecを作成しました。

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もあります。これは、さまざまな事前定義されたコマンドを追加でサポートします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.