歡迎您光臨本站 註冊首頁

如何使用Swift來實現一個命令列工具的方法

←手機掃碼閱讀     limiyoyo @ 2020-06-12 , reply:0

本文即簡單介紹瞭如何在Swift中開發命令列工具,以及與Shell命令的互動。水文一篇,不喜勿噴。
 

主要是使用該工具來解析微信的效能監控元件Matrix的OOM Log。
 

基本模組
 

這裡,僅簡單介紹了常見的基本模組。
 

Process
 

Process類可以用來開啟另外一個子程序,並監控其執行情況。

  1. launchPath:指定了執行路徑。如可以設定為 /usr/bin/env ,這個命令可以用於列印本機上所有的環境變數;也可以用於執行shell命令,如果你接了引數的話。本文的Demo就用它來執行輸入的命令。

  2. arguments:引數,以陣列形式傳遞即可。

  3. launch:呼叫launch函式即可啟動process,用於執行命令。

  4. waitUntilExit:一般執行Shell命令,需要等待命令返回。

  5. terminationStatus:當前process的結束狀態,正常為0.

  6. standardOutput:standardOutput對應於終端的標準輸出。standardError則是錯誤輸出。

Pipe
 

Pipe這個類就是作業系統的管道,在這裡用來接受子程序的輸出。這裡,可以用於將process的輸出傳遞至管道指定的地方,如一個output變數,或者檔案也可以。

  • fileHandleForReading:pipe從哪裡讀取內容?

  • fileHandleForWriting:pipe將內容寫到哪裡?

CommandLine
 

用於獲取指令碼引數而已。
 

  print(CommandLine.argc) // 2  print(CommandLine.arguments) // ["./test.swift", "hello"]

 

封裝Shell命令

僅執行Shell命令
 

這裡提供了兩種呼叫Shell命令的封裝函式,個人更傾向於第二種,直接將Shell命令及引數封裝成一個字串傳入即可。
 

  @discardableResult  func runShell(_ command: String) -> Int32 {   let task = Process()   task.launchPath = "/bin/bash"   task.arguments = ["-c", command]   task.launch()   task.waitUntilExit()   return task.terminationStatus  }    @discardableResult  func runShellWithArgs(_ args: String...) -> Int32 {   let task = Process()   task.launchPath = "/usr/bin/env"   task.arguments = args   task.launch()   task.waitUntilExit()   return task.terminationStatus  }

 

使用如下:
 

  runShell("pwd")  runShell("ls -l")    runShellWithArgs("pwd")  runShellWithArgs("ls", "-l")

 

需要Shell命令的輸出內容
 

這裡就需要使用到Pipe了。
 

  @discardableResult  func runShellAndOutput(_ command: String) -> (Int32, String?) {   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 (task.terminationStatus, output)  }    @discardableResult  func runShellWithArgsAndOutput(_ args: String...) -> (Int32, String?) {   let task = Process()     task.launchPath = "/usr/bin/env"   task.arguments = args      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 (task.terminationStatus, output)  }

 

使用如下:
 

  let (ret1, output1) = runShellAndOutput("ls -l")  if let output11 = output1 {   print(output11)  }    let (ret2, output2) = runShellWithArgsAndOutput("ls", "-l")  if let output22 = output2 {   print(output2)  }

 

如何解析Matrix的OOM Log
 

Matrix的OOM Log格式如下,其實就是一個大JSON:
 

  {   "head": {    "protocol_ver": 1,    "phone": "iPhone10,1",    "os_ver": "13.4",    "launch_time": 1589361495000,    "report_time": 1589362109100,    "app_uuid": ""   },   "items": [    {     "tag": "iOS_MemStat",     "info": "",     "scene": "",     "name": "Malloc 12.54 MiB",     "size": 146313216,     "count": 1,     "stacks": [      {       "caller": "f07199ac8a903127b17f0a906ffb0237@84128",       "size": 146313216,       "count": 1,       "frames": [        {         "uuid": "a0a7d67af0f3399a8f006f92716d8e6f",         "offset": 67308        },        {         "uuid": "a0a7d67af0f3399a8f006f92716d8e6f",         "offset": 69836        },        {         "uuid": "f07199ac8a903127b17f0a906ffb0237",         "offset": 84128        },        {         "uuid": "b80198f7beb93e79b25c7a27d68bb489",         "offset": 14934312        },        {         "uuid": "1a46239df2fc34b695bc9f38869f0c85",         "offset": 1126304        },        {         "uuid": "1a46239df2fc34b695bc9f38869f0c85",         "offset": 123584        },        {         "uuid": "1a46239df2fc34b695bc9f38869f0c85",         "offset": 1135100        }]      }     ]    }   ]  }

 

解析的思路其實非常簡單,將JSON轉為Model,然後根據所需,提取對應的資訊即可。
 

uuid是mach-o的唯一標識,offset則是符號相對於mach-o基地址的偏移量。拿到dSYM檔案,使用 atos 命令即可進行符號化。
 

  guard let rawLogModel = MatrixOOMLogParser.parse() else { exit(-1) }  print("______ Start to process Matrix OOM Log ...")    let group = DispatchGroup()    var metaLog = ""    for item in bodyInfo.items {   guard let stacks = item.stacks else { continue }      group.enter()      DispatchQueue.global().async {    var log = "______ item ______ name: (item.name), size: (item.size), count: (item.count)  "    metaLog += log        for stack in stacks {     let outputs = stack.frames.map({ (frame: MatrixOOMLogModelFrame) -> String in      // let uuid = frame.uuid      let offset = frame.offset      let instructionAddress = loadAddress + offset      let (_, output) = runShellAndOutput("xcrun atos -o (dwarf) -arch arm64 -l 0x1 (instructionAddress.hexValue)")      return output ?? ""     })          log += outputs.joined()          print(log)    }        group.leave()   }  }    group.wait()    print(" (metaLog) ")    print("______ Finished processing Matrix OOM Log ...")

 

MatrixOOMLogParser.parse() 就是將JSON轉為Model,這裡用的就是Swift裡邊的Codable。
 

這裡有一個需要注意的點,Mac CLI沒有Bundle的概念,只有一個bin檔案。所以對於原始的JSON檔案,只能透過外部bundle的方式來新增。透過 New->Target 單獨建立一個bundle。需要在 Xcode -> Build Phases -> Copy Files 中新增該bundle名,然後即可透過 Bundle(url: mockDataBundleURL) 來載入該bundle並獲取其中的log檔案了。
 

因為atos的執行時間較長,所以大量的符號化操作會非常耗時。一般來說,這段程式碼執行六七分鐘左右,可以將一個Matrix的OOM Log完全符號化。而符號化之後的記錄如何分析,就是另外一個話題了。
   


[limiyoyo ] 如何使用Swift來實現一個命令列工具的方法已經有297次圍觀

http://coctec.com/docs/program/show-post-238183.html