신속한 스크립트에서 터미널 명령을 어떻게 실행합니까? (예 : xcodebuild)


87

내 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 ....

답변:


136

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'는 '프로세스'로 이름이 바뀌 었습니다
마테우스

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
cyber8200

4
프로세스는 맥 OS로 볼 수 있습니다
shallowThought

85

모든 인수를 구분하지 않고 명령 줄에서와 같이 "정확하게"명령 줄 인수를 사용하려면 다음을 시도하십시오.

(이 답변은 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/bash"
    task.launch()

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

    return output
}

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

6
이 답변은 이전 문제의 많은 문제를 해결하기 때문에 실제로 훨씬 더 높아야합니다.
Steven Hepting

1
+1. 를 /bin/bash참조하는 osx 사용자의 경우주의해야합니다 bash-3.2. bash의 고급 기능을 사용하려면 경로를 변경하십시오 ( /usr/bin/env bash일반적으로 좋은 대안)
Aserre

누구든지 이것을 도울 수 있습니까? 인수가 stackoverflow.com/questions/62203978/…을
mahdi

34

여기서 문제는 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 코드는 xcodebuild인수와 함께 실행 된 다음 결과를 출력합니다.

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

디렉터리 내용 ( lsBash에서 수행하는 작업)을 NSFileManager검색하려면 구문 분석이 어려울 수있는 Bash 출력 대신 Swift에서 직접 디렉터리를 사용 하고 검색하는 것이 좋습니다 .


1
좋아-이 컴파일을 위해 몇 가지 편집을 수행했지만 호출하려고 할 때 런타임 예외가 발생합니다 shell("ls", []).- 'NSInvalidArgumentException', reason: 'launch path not accessible' 아이디어가 있습니까?
Robert

5
NSTask는 셸처럼 실행 파일을 검색 하지 않습니다 (환경에서 PATH 사용). 시작 경로는 절대 경로 (예 : "/ bin / ls")이거나 현재 작업 디렉토리에 상대적인 경로 여야합니다.
Martin R

stackoverflow.com/questions/386783/… PATH는 기본적으로 쉘의 개념이며 도달 할 수 없습니다.
레고리스

좋습니다. 이제 작동합니다. 완전성을 위해 전체 스크립트와 몇 가지 수정 사항을 게시했습니다. 감사합니다.
Robert

2
shell ( "cd", "~ / Desktop /")을 사용하면 다음과 같은 결과가 나타납니다. / usr / bin / cd : 줄 4 : cd : ~ / Desktop / : 해당 파일이나 디렉터리가 없습니다
Zaporozhchenko Oleksandr

21

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

5
import Foundation누락
Binarian

3
안타깝게도 iOS 용이 아닙니다.
Raphael

16

명령을 호출하기 위해 bash 환경을 사용하려면 고정 된 버전의 Legoless를 사용하는 다음 bash 함수를 사용하십시오. 쉘 함수의 결과에서 후행 개행을 제거해야했습니다.

스위프트 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)")

12

Legoless의 답변을 기반으로 한 전체 스크립트

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

10

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

예를 들어
Gowtham Sooryaraj

3

여기에 게시 된 몇 가지 솔루션을 시도한 후 명령을 실행하는 가장 좋은 방법 -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

Swift 3에 대한 rintaro와 Legoless의 답변 혼합

@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 변수 지원으로 약간의 개선 :

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"파일을 어디에 배치합니까
Suhaib Roomy

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.