iOS高級(jí)強(qiáng)化--014:Shell實(shí)戰(zhàn)解析

Xcode執(zhí)?腳本的三種?式
方式一

新建Empty工程,命名mode1

創(chuàng)建Target,選擇Aggregate,命名RunScript

點(diǎn)擊RunScript,選擇Build Phases,點(diǎn)擊+,選擇New Run Script Phase

名稱允許重命名,這里修改為CustomScript。文本框內(nèi)可輸入腳本代碼

支持創(chuàng)建多個(gè)Run Script Phase

方式二

新建External Build System工程,命名mode2

方式一有所不同,這里可以配置Build Tool、ArgumentsDirectory

例如:執(zhí)行一個(gè)上傳bugly的命令

java -jar buglySymboliOS.jar -i /Users/zang/Zang/Spark/buglySymboliOS3.0.0/lsj.dSYM -u -id 3a353e096f -key 42a9b82a-79a0-4120-beb4-8fba4d8exxxx -package com.xxxxx.fxxx -version 4.0.123

選擇info,進(jìn)行如下配置

  • Build Tool:配置命令
  • Arguments:配置參數(shù)
  • Directory:配置工作目錄

使用External Build System工程,在編譯階段,還可以看到日志的輸出

方式三

使用xcconfig文件,定義變量

xcode_run_cmd.sh文件,使用了xcconfig中的變量

方式三可以將腳本中的關(guān)鍵代碼和命令,在項(xiàng)目中使用xcconfig文件進(jìn)行控制。配合方式一.sh文件一起使用,相對(duì)更為靈活

實(shí)戰(zhàn)解析
案例1

完成一個(gè)簡(jiǎn)單的Shell腳本,可執(zhí)行Shell命令,將運(yùn)行結(jié)果或錯(cuò)誤信息輸出到終端

創(chuàng)建xcode_run_cmd.sh文件,寫入以下代碼:

聲明RunCMDToTTY函數(shù)

RunCMDToTTY() {

   if [[ -n "$1" ]]; then
       CMD="$1"
   fi

   if [[ -n "$2" ]]; then
       TTY="$2"
   fi

   if [[ ! -n "$TTY" ]]; then
       TTY=`eval "tty"`
   fi
   
   if [[ ! -n "$TTY" ]]; then
       EchoError "=========================================="
       EchoError "ERROR: Not Config tty to output."
       exit -1
   fi
   
   if [[ -n "$CMD" ]]; then
       RunCommand "$CMD"
   else
       EchoError "=========================================="
       EchoError "ERROR:Failed to run CMD. THE CMD must not null"
   fi
}
  • 判斷參數(shù)1非空,將參數(shù)1賦值給CMD變量
  • 判斷參數(shù)2非空,將參數(shù)2賦值給TTY變量
  • 判斷TTY變量為空,通過eval "tty"命令獲取終端標(biāo)識(shí)
  • 獲取終端標(biāo)識(shí)后,如果TTY變量為空,輸出錯(cuò)誤提示
  • 判斷CMD變量非空,調(diào)用RunCommand函數(shù),傳入CMD變量。否則輸出錯(cuò)誤提示

聲明RunCommand函數(shù)

declare VERBOSE_SCRIPT_LOGGING=""

RunCommand() {

 if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
     echo "? $@" 1>$TTY
     echo "-------------------------" 1>$TTY
 fi

 echo `$@ &>$TTY`

 return $?
}
  • 定義VERBOSE_SCRIPT_LOGGING全局變量,控制是否輸出所有參數(shù),用于調(diào)試腳本
  • 如果VERBOSE_SCRIPT_LOGGING變量非空,輸出所有參數(shù)和分割線
  • 通過echo + 反引號(hào)執(zhí)行命令并輸出
  • 顯示最后命令的退出狀態(tài)。0表示沒有錯(cuò)誤,其他任何值表明有錯(cuò)誤

聲明EchoError函數(shù)

EchoError() {
   if [[ -n "$TTY" ]]; then
       echo "$@" 1>&2>$TTY
   else
       echo "$@" 1>&2
   fi
}
  • 1>&2:將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)錯(cuò)誤輸出,就是以標(biāo)準(zhǔn)錯(cuò)誤格式打印所有參數(shù)
  • 如果TTY參數(shù)非空,通過終端標(biāo)識(shí)輸出到指定終端窗口

測(cè)試xcode_run_cmd.sh腳本

只傳入命令,將結(jié)果輸出在當(dāng)前終端窗口

./xcode_run_cmd.sh 'ls -a'
-------------------------
.          .DS_Store           shell
..         Common Symbol       xcode_run_cmd.sh

傳入命令和終端標(biāo)識(shí),將結(jié)果輸出到指定終端標(biāo)識(shí)窗口

新開一個(gè)終端窗口,使用tty獲取終端標(biāo)識(shí)

在原始窗口輸入./xcode_run_cmd.sh 'ls -a' '/dev/ttys003'命令

配合Xcode使用

搭建一個(gè)項(xiàng)目,將xcode_run_cmd.sh腳本拷貝到項(xiàng)目根目錄

創(chuàng)建xcconfig文件,并配置到Tatget上,寫入以下代碼:

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}
CMD = objdump --macho --syms ${MACH_PATH}
TTY=/dev/ttys003
  • MACHO_PATH:定義變量,存儲(chǔ)Mach-O文件的路徑
  • 定義CMDTTY變量,以供xcode_run_cmd.sh腳本使用

點(diǎn)擊Target,選擇Build Phases,在Run Script中輸入:/bin/sh "$SRCROOT/xcode_run_cmd.sh"

項(xiàng)目編譯后,自動(dòng)將Mach-O中的符號(hào)展示到終端,無(wú)需手動(dòng)操作

附上完整xcode_run_cmd.sh腳本

#!/bin/sh

declare VERBOSE_SCRIPT_LOGGING=""

RunCommand() {

 if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
     echo "? $@" 1>$TTY
     echo "-------------------------" 1>$TTY
 fi

 echo `$@ &>$TTY`

 return $?
}

EchoError() {
   if [[ -n "$TTY" ]]; then
       echo "$@" 1>&2>$TTY
   else
       echo "$@" 1>&2
   fi
}

RunCMDToTTY() {

   if [[ -n "$1" ]]; then
       CMD="$1"
   fi

   if [[ -n "$2" ]]; then
       TTY="$2"
   fi

   if [[ ! -n "$TTY" ]]; then
       TTY=`eval "tty"`
   fi
   
   if [[ ! -n "$TTY" ]]; then
       EchoError "=========================================="
       EchoError "ERROR: Not Config tty to output."
       exit -1
   fi
   
   if [[ -n "$CMD" ]]; then
       RunCommand "$CMD"
   else
       EchoError "=========================================="
       EchoError "ERROR:Failed to run CMD. THE CMD must not null"
   fi
}

RunCMDToTTY "$@"
案例2

完成一個(gè)相對(duì)復(fù)雜的Shell腳本。指定目錄,指定文件格式,在文件內(nèi)容中搜索關(guān)鍵字,最終列出包含關(guān)鍵字的文件列表

演示腳本功能:

使用sh find_api.sh --help命令

find_api.sh --directory <dir> 在指定目錄指定文件內(nèi)搜索指定關(guān)鍵字。

   -d|--directory <dir> - 指定查找目錄,默認(rèn)當(dāng)前所在目錄
   -k|--keyword <word> - 查找關(guān)鍵字
   -s|--source  - 指定查找源碼文件
   -f|--framework - 指定查找framework文件
   -l|--lib - 指定查找libs文件
   --help  - prints help screen
  • 可指定目錄
  • 可指定文件格式
  • 支持長(zhǎng)參數(shù),例如:--keyword
  • 支持短參數(shù),例如:-k
  • 可指定多個(gè)搜索關(guān)鍵字

原理:在源碼文件中,可以直接使用grep搜索內(nèi)容,但在目標(biāo)文件、靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)中,需要搜索符號(hào)表中的信息

演示執(zhí)行效果:

.xcframework中,指定源碼文件、frameworklibs三種文件格式,找到包含mainAF關(guān)鍵字的文件列表

【步驟一】

定義變量

#!/bin/sh

declare DIRECTORY="."
declare SOURCE=""
declare FRAMEWORK=""
declare LIB=""
declare KEYWORD=""
  • DIRECTORY:指定搜索的目錄
  • SOURCE:是否搜索源碼文件
  • FRAMEWORK:是否搜索framework
  • LIB:是否搜索靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)
  • KEYWORD:搜索關(guān)鍵字

參數(shù)解析

while [[ $# -gt 0 ]]; do
   case "$1" in
   -d|--directory)
       shift
       DIRECTORY="$1"
       shift
       ;;
   -k|--keyword)
       shift
       if [[ -n $1 ]]; then
           KEYWORD="${KEYWORD}$1\n"
       fi
       shift
       ;;
   -s|--source)
       SOURCE="1"
       shift
       ;;
   -f|--framework)
       FRAMEWORK="1"
       shift
       ;;
   -l|--lib)
       LIB="1"
       shift
       ;;
   -h|--help)
       show_usage
       exit 0
       ;;
   *)
       echo "Unknown option: $1"
       exit 1
   esac
done
  • $#:傳入的參數(shù)的個(gè)數(shù)
  • -gt:大于
  • shift:使參數(shù)向右發(fā)生位移,每次調(diào)用shift時(shí),它將所有位置上的參數(shù)-1
  • exit:退出當(dāng)前Shell進(jìn)程,并返回一個(gè)退出狀態(tài)。退出狀態(tài)為0表示成功,退出狀態(tài)為非0表示失敗

代碼邏輯:

  • 定義五個(gè)變量
  • 當(dāng)參數(shù)個(gè)數(shù)大于0循環(huán)遍歷參數(shù)
  • 每次獲取$1進(jìn)行參數(shù)匹配
  • 命中-d--directory,使用shift讓參數(shù)位置-1,然后再次獲取$1將指定目錄賦值給變量,再使用shift讓參數(shù)位置-1
  • 命中-k、--keyword,同理,獲取$1將指定關(guān)鍵字賦值給變量
  • 命中-s、--source,將搜索源碼文件的標(biāo)識(shí)設(shè)置為1,使用shift讓參數(shù)位置-1
  • 命中-f、--framework,同理,將搜索framework的標(biāo)識(shí)設(shè)置為1
  • 命中-l、--lib,同理,將搜索靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)的標(biāo)識(shí)設(shè)置為1
  • 命中-h、--help,調(diào)用show_usage函數(shù),退出當(dāng)前Shell進(jìn)程,并返回0表示成功
  • 以上均未命中,將參數(shù)輸出,退出當(dāng)前Shell進(jìn)程,并返回1表示失敗
  • 其中-k、--keyword支持多個(gè)參數(shù),這里使用換行符,將多個(gè)關(guān)鍵字拼接到一起

【第二步】

聲明show_usage函數(shù)

function show_usage() {

   local help=$(cat <<EOF

       find_api.sh --directory <dir> 在指定目錄指定文件內(nèi)搜索指定關(guān)鍵字。

           -d|--directory <dir> - 指定查找目錄,默認(rèn)當(dāng)前所在目錄
           -k|--keyword <word> - 查找關(guān)鍵字
           -s|--source - 指定查找源碼文件
           -f|--framework - 指定查找framework文件
           -l|--lib - 指定查找libs文件
           -h|--help - prints help screen
EOF)
   echo "$help"
}
  • EOF只是一個(gè)標(biāo)識(shí)而已,可以替換成任意的合法字符
  • EOF作為結(jié)尾的標(biāo)識(shí)一定要頂格寫,前面不能有任何字符
  • EOF作為結(jié)尾的標(biāo)識(shí)后面也不能有任何的字符(包括空格)
  • EOF作為起始的標(biāo)識(shí)前后的空格會(huì)被省略掉
  • 使用$()包裝成命令,作用與反引號(hào)一樣,此處不加也行

代碼邏輯:

  • 聲明show_usage函數(shù),參數(shù)命中-h、--help時(shí),輸出幫助信息
  • 定義help本地變量
  • 使用$()包裝成命令,賦值給help變量
  • 使用cat <<,將帶有結(jié)束標(biāo)志的文檔內(nèi)容傳遞到命令的標(biāo)準(zhǔn)輸入
  • 開始的EOF作為起始標(biāo)識(shí)
  • 最后的EOF作為結(jié)尾標(biāo)識(shí)
  • 使用echohelp變量輸出

【第三步】

明確find_api.sh的兩個(gè)核心原理:

  • 在目錄中找到指定格式的文件
  • 在文件中搜索指定關(guān)鍵字

在目錄中找到指定格式的文件

find命令:從指定的起始目錄開始,遞歸地搜索其各個(gè)子目錄,查找滿足尋找條件的文件并對(duì)之采取相關(guān)的操作

.xcframework文件中,遞歸搜索.framework文件

find ./mm.xcframework -name "*.framework"
-------------------------
./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework
./mm.xcframework/SYTimer.xcframework/ios-arm64_armv7/SYTimer.framework
  • -name:查找文件名匹配字符串的所有文件,可用通配符*、?、[]

.xcframework文件中,遞歸搜索.a文件和.o文件

find ./mm.xcframework \( -name "*.a" -o -name "*.o" \)
-------------------------
./mm.xcframework/test.o
./mm.xcframework/libAFNetworking.a
  • find命令提供的尋找條件可以是一個(gè)用邏輯運(yùn)算符not、and、or組成的復(fù)合條件
  • or:邏輯或,在命令中用-o表示。該運(yùn)算符表示只要所給的條件中有一個(gè)滿足時(shí),尋找條件就算滿足
  • and:邏輯與,在命令中用-a表示,是系統(tǒng)缺省的選項(xiàng),表示只有當(dāng)所給的條件都滿足時(shí),尋找條件才算滿足
  • not:邏輯非,在命令中用!表示。該運(yùn)算符表示查找不滿足所給條件的文件
  • 當(dāng)使用很多的邏輯選項(xiàng)時(shí),可以用括號(hào)把這些選項(xiàng)括起來(lái)。為了避免Shell本身對(duì)括號(hào)引起誤解,在括號(hào)前需要加轉(zhuǎn)義字符\來(lái)去除括號(hào)的意義

-exec 命令名稱 {}:對(duì)符合條件的文件執(zhí)行所給的命令,而不詢問用戶是否需要執(zhí)行該命令

find ./mm.xcframework \( -name "*.a" -o -name "*.o" \) -exec echo {} \;
-------------------------
./mm.xcframework/test.o
./mm.xcframework/libAFNetworking.a
  • {}:表示命令的參數(shù)即為所找到的文件
  • 命令的末尾必須加上終結(jié)符,終結(jié)符有;+兩種。其中;會(huì)對(duì)每一個(gè)find到的文件去執(zhí)行一次cmd命令。而+find到的文件一次性執(zhí)行完cmd命令

在文件中搜索指定關(guān)鍵字

如果在.h、.m、.swift文件格式中搜索,可以直接使用grep命令

grep命令:在文件中搜索關(guān)鍵字,命令會(huì)返回一個(gè)包含關(guān)鍵字的文本行

test.m文件中,搜索@"SomeNewFunction_weak_import"

grep "@\"SomeNewFunction_weak_import\"" ./mm.xcframework/test.m
-------------------------
   NSLog(@"SomeNewFunction_weak_import");

使用-A 1參數(shù)輸出結(jié)果之后一行,使用-B 1參數(shù)輸出結(jié)果之前一行

grep "@\"SomeNewFunction_weak_import\"" -A 1 -B 1 ./mm.xcframework/test.m
-------------------------
void SomeNewFunction_weak_import(void) {
   NSLog(@"SomeNewFunction_weak_import");
}

使用-i參數(shù),忽略字符大小寫的差別

grep "@\"somenewfunction_weak_import\"" -A 1 -B 1 -i ./mm.xcframework/test.m
-------------------------
void SomeNewFunction_weak_import(void) {
   NSLog(@"SomeNewFunction_weak_import");
}

使用-E參數(shù),使用正則表達(dá)式搜索關(guān)鍵字

grep -E "LG_Cat-1|LG_Cat-2" -A 1 -B 1 -i ./mm.xcframework/test.m
-------------------------
   // 外部
   NSLog(@"LG_Cat-1");
   int a[4] = {1,2,3,4};
--
--
   int a[4] = {1,2,3,4};
   NSLog(@"LG_Cat-2");
   int m = 10;

如果在目標(biāo)文件、靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)中搜索,需要使用nm命令

nm命令:被用于顯示二進(jìn)制目標(biāo)文件的符號(hào)表

SYTimer動(dòng)態(tài)庫(kù)的符號(hào)表中搜索關(guān)鍵字

nm -pa ./mm.xcframework/SYTimer.framework/SYTimer | grep -E "nextFireTime"
-------------------------
000057b4 t -[SYTimerBase(Private) nextFireTime]
0000000000006f08 t -[SYTimerBase(Private) nextFireTime]

【第四步】

拼接KEYWORD

無(wú)論使用grep命令還是nm命令,在搜索關(guān)鍵字時(shí)都會(huì)使用正則的匹配格式,所以需要將KEYWORD按照key1|key2|key3的格式拼接

【步驟一】中,已經(jīng)將多個(gè)關(guān)鍵字按照回車符進(jìn)行拼接,這里聲明Find_Api函數(shù),在主函數(shù)中進(jìn)行二次處理

read命令:從鍵盤讀取變量的值,通常用在Shell腳本中與用戶進(jìn)行交互的場(chǎng)合。該命令可以一次讀取多個(gè)變量的值,變量和輸入的值都需要使用空格隔開。在read命令后面,如果沒有指定變量名,讀取的數(shù)據(jù)將被自動(dòng)賦值給特定的變量REPLY

  • -a:后跟一個(gè)變量,該變量會(huì)被認(rèn)為是個(gè)數(shù)組,然后給其賦值,默認(rèn)是以空格為分割符
  • -r:屏蔽\,如果沒有該選項(xiàng),則\作為一個(gè)轉(zhuǎn)義字符,有的話\就是個(gè)正常的字符

read通過輸入重定向,把file的第一行所有的內(nèi)容賦值給變量line,循環(huán)體內(nèi)的命令一般包含對(duì)變量line的處理;然后循環(huán)處理file的第二行、第三行。。。一直到file的最后一行

read命令也有退出狀態(tài),當(dāng)它從文件file中讀到內(nèi)容時(shí),退出狀態(tài)為0,循環(huán)繼續(xù)進(jìn)行。當(dāng)read從文件中讀完最后一行后,下次便沒有內(nèi)容可讀了,此時(shí)read的退出狀態(tài)為非0,所以循環(huán)才會(huì)退出

while read linefor循環(huán)的區(qū)別:

while read line是一次性將文件信息讀入并按行賦值給變量line,while中使用重定向機(jī)制,文件中的所有信息都被讀入并重定向給了整個(gè)while語(yǔ)句中的line變量

坑點(diǎn)一:

使用echo輸出KEYWORD,通過管道,將內(nèi)容作為while read命令的標(biāo)準(zhǔn)輸入

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請(qǐng)輸入查找的關(guān)鍵字!"
       exit 1
   fi

   local key_word=""

   echo ${KEYWORD} | while read name; do
       if [[ ! -n "${name}" ]]; then
           continue
       fi

       if [[ -n "${key_word}" ]]; then
           key_word="${key_word}|${name}"
       else
           key_word="${name}"
       fi

       echo "${key_word}------內(nèi)部"
   done

   echo "${key_word}------外部"
}

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
cat------內(nèi)部
cat|kc------內(nèi)部
cat|kc|hk------內(nèi)部
cat|kc|hk|kd------內(nèi)部
------外部
  • while循環(huán)中的打印沒有任何問題,但是在循環(huán)之外打印,key_word的值莫名其妙的置空了

上述問題,因?yàn)槭褂霉艿蓝a(chǎn)生

  • 在大多數(shù)Shell中(包括bash),管道的每一側(cè)都在子Shell中運(yùn)行,因此,Shell內(nèi)部狀態(tài)的任何更改(例如,設(shè)置變量)都僅限于管道的該段。您可以從子Shell上獲得的唯一信息是它的輸出(到標(biāo)準(zhǔn)輸出和其他文件描述符)及其退出代碼(0255之間的數(shù)字)
  • 當(dāng)打開一個(gè)子Shell時(shí),父Shell里面中的系統(tǒng)環(huán)境變量會(huì)被復(fù)制到子Shell中(用export定義的變量才是系統(tǒng)環(huán)境變量)
  • 一個(gè)Shell中的系統(tǒng)環(huán)境變量只對(duì)該Shell或者它的子Shell有效,該Shell結(jié)束時(shí)變量消失,并不能返回到父Shell
  • 不用export定義的變量只對(duì)該Shell有效,對(duì)子Shell也是無(wú)效的
  • 直接執(zhí)行一個(gè)腳本文件是在一個(gè)子Shell中運(yùn)行的,而在腳本前加source,則是在當(dāng)前Shell環(huán)境中直接運(yùn)行(不是子Shell

坑點(diǎn)二:

解決子Shell問題,需要避免管道的使用

修改方案,使用<<<,表示將右側(cè)的字符串傳遞到左側(cè)命令的標(biāo)準(zhǔn)輸入

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請(qǐng)輸入查找的關(guān)鍵字!"
       exit 1
   fi

   local key_word=""

   while read name; do

       if [[ ! -n "${name}" ]]; then
           continue
       fi

       if [[ -n "${key_word}" ]]; then
           key_word="${key_word}|${name}"
       else
           key_word="${name}"
       fi
       
       echo ${key_word}

   done <<< "${KEYWORD}"
}

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
catnkcnhknkdn
  • 文本中間的\n,沒有被識(shí)別為換行,而是被當(dāng)做\n輸出了,所以while read逐行讀取沒有生效

解決辦法:

定義tmp本地變量,使用echoKEYWORD進(jìn)行輸出,將結(jié)果賦值給tmp

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請(qǐng)輸入查找的關(guān)鍵字!"
       exit 1
   fi

   local tmp=$(echo ${KEYWORD})

   local key_word=""

   while read name; do

       if [[ ! -n "${name}" ]]; then
           continue
       fi

       if [[ -n "${key_word}" ]]; then
           key_word="${key_word}|${name}"
       else
           key_word="${name}"
       fi

   done <<< "${tmp}"

   echo ${key_word}
}

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
cat|kc|hk|kd
  • 結(jié)果符合預(yù)期,問題完美解決

【第五步】

拼接將要搜索的文件格式

declare SOURCE_EXTENSION='*.h *.m *.mm *.c *.hpp *.cpp *.swift *.xcconfig'
declare FRAMEWORK_EXTENSION='*.framework *.o *.tbd'
declare LIB_EXTENSION='*.a *.dylib'
  • SOURCE_EXTENSION:指定源碼文件包含的文件格式
  • FRAMEWORK_EXTENSION:指定framework包含的文件格式
  • LIB_EXTENSION:指定libs包含的文件格式
   local find_name=""

   if [[ -n "${SOURCE}" ]]; then
       find_name="${SOURCE_EXTENSION}"
   fi
       
   if [[ -n "${FRAMEWORK}" ]]; then
       find_name="${find_name} ${FRAMEWORK_EXTENSION}"
   fi
       
   if [[ -n "${LIB}" ]]; then
       find_name="${find_name} ${LIB_EXTENSION}"
   fi
       
   if [[ ! -n "${find_name}" ]]; then
       find_name="${SOURCE_EXTENSION} ${FRAMEWORK_EXTENSION} ${LIB_EXTENSION}"
   fi

   echo "${find_name}------"
  • 定義find_name本地變量
  • 如果指定SOURCE,將SOURCE_EXTENSION賦值給find_name
  • 如果指定FRAMEWORK,追加FRAMEWORK_EXTENSION內(nèi)容
  • 如果指定LIB,追加LIB_EXTENSION內(nèi)容
  • 如果最終指定的find_name為空,默認(rèn)搜索全部格式

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
*.h *.m *.mm *.c *.hpp *.cpp *.swift *.xcconfig *.framework *.o *.tbd *.a *.dylib

將結(jié)果按照find命令的參數(shù)格式拼接:-name p1 -o -name p2 -o -name p3...

坑點(diǎn)一:

   local need_name=""

   for name in ${find_name}
   do
       if [[ ! -n "${need_name}" ]]; then
           need_name="-name \${name}"
       else
           need_name="${need_name} -o -name ${name}"
       fi
          
   done

   echo ${need_name}

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
-name *.h -o -name *.m -o -name *.mm -o -name *.c -o -name *.hpp -o -name *.cpp -o -name *.swift -o -name *.xcconfig -o -name ws.framework -o -name *.o -o -name *.tbd -o -name *.a -o -name *.dylib
  • 因?yàn)槟夸浿写嬖?code>ws.framework文件,和find_api.sh平級(jí)。導(dǎo)致原本的*.framework輸出變成了ws.framework

使用set -x命令,顯示該指令及所下的參數(shù)

set -x
find . -name *.framework
-------------------------
+ find . -name ws.framework
  • 不加引號(hào)的*,首先會(huì)被bash進(jìn)行擴(kuò)展,所以find . -name *.framework在執(zhí)行find命令前,bash先把*.framework替換成了ws.framework,然后find命令看到的參數(shù)實(shí)際上是ws.framework
set -x 
find . -name "*.framework"
-------------------------
+ find . -name '*.framework'
  • 加了引號(hào),bash就不去做替換了,那么find命令看到的參數(shù)就是*.framework

坑點(diǎn)二:

修改代碼,在拼接name變量時(shí),前后加上\"

       if [[ ! -n "${need_name}" ]]; then
           need_name="-name \"${name}\""
       else
           need_name="${need_name} -o -name \"${name}\""
       fi

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
-name "*.h" -o -name "*.m" -o -name "*.mm" -o -name "*.c" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.swift" -o -name "*.xcconfig" -o -name "ws.framework" -o -name "*.o" -o -name "*.tbd" -o -name "*.a" -o -name "*.dylib"
  • 問題并沒有解決,輸出內(nèi)容變成了"ws.framework"

這里還存在另一個(gè)問題,循環(huán)時(shí)使用的for name in ${find_name},它會(huì)將find_name的內(nèi)容以空格分割,此時(shí)*.framework已經(jīng)被擴(kuò)展為ws.framework

解決辦法:

使用read -a命令,指定find_name為數(shù)組

   local need_name=""

   read -a find_name <<< "$find_name"

   for name in "${find_name[@]}"
   do
       if [[ ! -n "${need_name}" ]]; then
           need_name="-name \"${name}\""
       else
           need_name="${need_name} -o -name \"${name}\""
       fi
          
   done

   echo ${need_name}

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd"
-------------------------
-name "*.h" -o -name "*.m" -o -name "*.mm" -o -name "*.c" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.swift" -o -name "*.xcconfig" -o -name "*.framework" -o -name "*.o" -o -name "*.tbd" -o -name "*.a" -o -name "*.dylib"

【第六步】

使用find命令,獲取文件列表

find $DIRECTORY  \( $need_name \)

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -d ./mm.xcframework -k "main"
-------------------------

  • 沒有輸出任何結(jié)果

使用set -x命令,顯示該指令及所下的參數(shù)

set -x 
find $DIRECTORY  \( $need_name \)
-------------------------
+ find ./mm.xcframework '(' -name '"*.h"' -o -name '"*.m"' -o -name '"*.mm"' -o -name '"*.c"' -o -name '"*.hpp"' -o -name '"*.cpp"' -o -name '"*.swift"' -o -name '"*.xcconfig"' -o -name '"*.framework"' -o -name '"*.o"' -o -name '"*.tbd"' -o -name '"*.a"' -o -name '"*.dylib"' ')'
  • 找到問題所在,文件格式被引號(hào)包裹兩層。例如*.h,被包裹成'"*.h"'

使用eval命令,用于重新運(yùn)算求出參數(shù)的內(nèi)容

set -x 
eval "find $DIRECTORY  \( $need_name \)"
-------------------------
+ eval 'find ./mm.xcframework  \( -name "*.h" -o -name "*.m" -o -name "*.mm" -o -name "*.c" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.swift" -o -name "*.xcconfig" -o -name "*.framework" -o -name "*.o" -o -name "*.tbd" -o -name "*.a" -o -name "*.dylib" \)'
++ find ./mm.xcframework '(' -name '*.h' -o -name '*.m' -o -name '*.mm' -o -name '*.c' -o -name '*.hpp' -o -name '*.cpp' -o -name '*.swift' -o -name '*.xcconfig' -o -name '*.framework' -o -name '*.o' -o -name '*.tbd' -o -name '*.a' -o -name '*.dylib' ')'
./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework
./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYTimer.h
./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYShareTimer.h
./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYRunLoop.h

...
  • 掃描第一次將外面的雙引號(hào)去掉
  • 掃描第二次作為find命令的參數(shù),執(zhí)行成功,輸出文件列表

【第七步】

遍歷文件列表,搜索關(guān)鍵字

   for file in $(eval "find $DIRECTORY  \( $need_name \)")
   do
       echo "${file}"
   done

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -d ./mm.xcframework -k "main"
-------------------------
./mm.xcframework/SYTimer.xcframework/ios-arm64_armv7/SYTimer.framework/Headers/SYThreadTimers.h
./mm.xcframework/Target
Support
Files/Pods-LoginApp/Pods-LoginApp.debug.xcconfig

...
  • 對(duì)于中間包含空格的目錄,原本是Target Support Files,但遍歷時(shí),for循環(huán)按空格切分,導(dǎo)致目錄被拆分為多條

使用while read命令,代替for循環(huán)

   local files=$(eval "find $DIRECTORY  \( $need_name \)")

   while read file; do
       echo "${file}"
   done <<< "${files}"
-------------------------
./mm.xcframework/SYTimer.xcframework/ios-arm64_armv7/SYTimer.framework/Headers/SYThreadTimers.h
./mm.xcframework/Target Support Files/Pods-LoginApp/Pods-LoginApp.debug.xcconfig
./mm.xcframework/test.o

...

問題完美解決,Target Support Files作為整體路徑被輸出

搜索關(guān)鍵字,有以下三種情況:

  • 對(duì)于源碼文件,可以直接使用grep命令
  • 對(duì)于.o.a文件,需要使用nm命令查找符號(hào)表
  • 對(duì)于.framework文件,也是目錄格式,需要先進(jìn)入x.framework目錄,對(duì)x進(jìn)行符號(hào)表查找
       if [[ -d "${file}" ]]; then
           local name=`basename "$file"`
           pushd "${file}" > /dev/null
           if nm -pa "${name/.framework}" | grep -E --color=auto "$key_word"; then
               echo "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
           fi
           popd > /dev/null
       else

           local is_source=""

           for source in ${SOURCE_EXTENSION}
           do
               if [[ "*.${file##*.}" = "${source}" ]]; then
                   is_source="1"
                   break
               fi
           done

           if [[ -n ${is_source} ]]; then
               if grep -E --color=auto "${key_word}" "${file}"; then
                   echo  "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
               fi
           else
               if nm -pa "${file}" | grep -E --color=auto "$key_word"; then
                   echo "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
               fi
           fi
       fi

代碼邏輯:

  • 如果是目錄,通過basename命令獲取文件名
  • 使用pushd命令,將目錄添加到目錄堆棧頂部
  • 使用參數(shù)擴(kuò)展去掉文件名的.framework后綴,通過nm命令查找符號(hào)表
  • 使用popd命令,從目錄堆棧中刪除目錄
  • 如果是文件,判斷文件格式是否屬于源碼文件
  • 如果是源碼文件,通過grep命令搜索關(guān)鍵字
  • 如果非源碼文件,通過nm命令查找符號(hào)表

測(cè)試腳本的輸出結(jié)果:

sh find_api.sh -d ./mm.xcframework -k "main"
-------------------------
0000000000005527 t +[SYRunLoop main]
0000000000006f4c t +[SYTimer mainRunLoopTimerWithRunLoopMode:block:]
000000000000f5d0 b __ZL13s_mainRunLoop
                U __dispatch_main_q
                U _pthread_main_np
在文件 ./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework 中找到了(main)關(guān)鍵字!



+ (instancetype)main;
在文件 ./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYRunLoop.h 中找到了(main)關(guān)鍵字!



/// Initializes a new SYTimer object using the block as the main body of execution for the timer. This timer will scheduled on main run loop.
+ (instancetype)mainRunLoopTimerWithRunLoopMode:(CFRunLoopMode)runLoopMode
在文件 ./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYTimerBase.h 中找到了(main)關(guān)鍵字!

...

附上完整find_api.sh腳本

#!/bin/sh

declare SOURCE=""
declare FRAMEWORK=""
declare LIB=""
declare DIRECTORY="."
declare KEYWORD=""

declare SOURCE_EXTENSION='*.h *.m *.mm *.c *.hpp *.cpp *.swift *.xcconfig'
declare FRAMEWORK_EXTENSION='*.framework *.o *.tbd'
declare LIB_EXTENSION='*.a *.dylib'

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請(qǐng)輸入查找的關(guān)鍵字!"
       exit 1
   fi

   local tmp=$(echo ${KEYWORD})

   local key_word=""

   while read name; do

       if [[ ! -n "${name}" ]]; then
           continue
       fi

       if [[ -n "${key_word}" ]]; then
           key_word="${key_word}|${name}"
       else
           key_word="${name}"
       fi

   done <<< "${tmp}"

   local find_name=""

   if [[ -n "${SOURCE}" ]]; then
       find_name="${SOURCE_EXTENSION}"
   fi
       
   if [[ -n "${FRAMEWORK}" ]]; then
       find_name="${find_name} ${FRAMEWORK_EXTENSION}"
   fi
       
   if [[ -n "${LIB}" ]]; then
       find_name="${find_name} ${LIB_EXTENSION}"
   fi
       
   if [[ ! -n "${find_name}" ]]; then
       find_name="${SOURCE_EXTENSION} ${FRAMEWORK_EXTENSION} ${LIB_EXTENSION}"
   fi

   local need_name=""

   read -r -a find_name <<< "$find_name"

   for name in "${find_name[@]}"
   do
       if [[ ! -n "${need_name}" ]]; then
           need_name="-name \"${name}\""
       else
           need_name="${need_name} -o -name \"${name}\""
       fi
          
   done

   local file_count=0
   local find_count=0
   local files=$(eval "find $DIRECTORY  \( $need_name \)")

   while read file; do

       file_count=$(( file_count + 1 ))

       if [[ -d "${file}" ]]; then
           local name=`basename "$file"`
           pushd "${file}" > /dev/null
           if nm -pa "${name/.framework}" | grep -E --color=auto "$key_word"; then
               find_count=$(( find_count + 1 ))
               echo "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
           fi
           popd > /dev/null
       else

           local is_source=""

           for source in ${SOURCE_EXTENSION}
           do
               if [[ "*.${file##*.}" = "${source}" ]]; then
                   is_source="1"
                   break
               fi
           done

           if [[ -n ${is_source} ]]; then
               if grep -E --color=auto "${key_word}" "${file}"; then
                   find_count=$(( find_count + 1 ))
                   echo  "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
               fi
           else
               if nm -pa "${file}" | grep -E --color=auto "$key_word"; then
                   find_count=$(( find_count + 1 ))
                   echo "在文件 \033[37;32;4m${file}\033[39;49;0m 中找到了(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!\n\n\n"
               fi
           fi
       fi

   done <<< "${files}"

   echo "共掃描 \033[37;32;4m${file_count}\033[39;49;0m 個(gè)文件,發(fā)現(xiàn) \033[37;32;4m${find_count}\033[39;49;0m 個(gè)文件包含(\033[37;31;4m${key_word}\033[39;49;0m)關(guān)鍵字!"
}

function show_usage() {

   local help=$(cat <<EOF

       find_api.sh --directory <dir> 在指定目錄指定文件內(nèi)搜索指定關(guān)鍵字。

           -d|--directory <dir> - 指定查找目錄,默認(rèn)當(dāng)前所在目錄
           -k|--keyword <word> - 查找關(guān)鍵字
           -s|--source - 指定查找源碼文件
           -f|--framework - 指定查找framework文件
           -l|--lib - 指定查找libs文件
           -h|--help - prints help screen
EOF)
   echo "$help"
}

while [[ $# -gt 0 ]]; do
   case "$1" in
   -d|--directory)
       shift
       DIRECTORY="$1"
       shift
       ;;
   -k|--keyword)
       shift

       if [[ -n $1 ]]; then
           KEYWORD="${KEYWORD}$1\n"
       fi

       shift
       ;;
   -s|--source)
       SOURCE="1"
       shift
       ;;
   -f|--framework)
       FRAMEWORK="1"
       shift
       ;;
   -l|--lib)
       LIB="1"
       shift
       ;;
   -h|--help)
       show_usage
       exit 0
       ;;
   *)
       echo "Unknown option: $1"
       exit 1
   esac
done

Find_Api
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容