iOS高級強(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 ToolArgumentsDirectory

例如:執(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文件一起使用,相對更為靈活

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

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

創(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)識
  • 獲取終端標(biāo)識后粘咖,如果TTY變量為空蚣抗,輸出錯誤提示
  • 判斷CMD變量非空,調(diào)用RunCommand函數(shù)瓮下,傳入CMD變量翰铡。否則輸出錯誤提示

聲明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 + 反引號執(zhí)行命令并輸出
  • 顯示最后命令的退出狀態(tài)。0表示沒有錯誤路呜,其他任何值表明有錯誤

聲明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)錯誤輸出迷捧,就是以標(biāo)準(zhǔn)錯誤格式打印所有參數(shù)
  • 如果TTY參數(shù)非空,通過終端標(biāo)識輸出到指定終端窗口

測試xcode_run_cmd.sh腳本

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

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

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

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

在原始窗口輸入./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:定義變量晌该,存儲Mach-O文件的路徑
  • 定義CMDTTY變量肥荔,以供xcode_run_cmd.sh腳本使用

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

項(xiàng)目編譯后燕耿,自動將Mach-O中的符號展示到終端中符,無需手動操作

附上完整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è)相對復(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
  • 可指定目錄
  • 可指定文件格式
  • 支持長參數(shù),例如:--keyword
  • 支持短參數(shù)亚再,例如:-k
  • 可指定多個(gè)搜索關(guān)鍵字

原理:在源碼文件中郭膛,可以直接使用grep搜索內(nèi)容,但在目標(biāo)文件氛悬、靜態(tài)庫则剃、動態(tài)庫中,需要搜索符號表中的信息

演示執(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)庫己肮、動態(tài)庫
  • 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è)置為1敷存,使用shift讓參數(shù)位置-1
  • 命中-f--framework堪伍,同理锚烦,將搜索framework的標(biāo)識設(shè)置為1
  • 命中-l觅闽、--lib,同理涮俄,將搜索靜態(tài)庫蛉拙、動態(tài)庫的標(biāo)識設(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)識而已令野,可以替換成任意的合法字符
  • EOF作為結(jié)尾的標(biāo)識一定要頂格寫舀患,前面不能有任何字符
  • EOF作為結(jié)尾的標(biāo)識后面也不能有任何的字符(包括空格)
  • EOF作為起始的標(biāo)識前后的空格會被省略掉
  • 使用$()包裝成命令,作用與反引號一樣气破,此處不加也行

代碼邏輯:

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

【第三步】

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

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

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

find命令:從指定的起始目錄開始,遞歸地搜索其各個(gè)子目錄碳锈,查找滿足尋找條件的文件并對之采取相關(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í)弄跌,可以用括號把這些選項(xiàng)括起來尝苇。為了避免Shell本身對括號引起誤解铛只,在括號前需要加轉(zhuǎn)義字符\來去除括號的意義

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

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

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

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

grep命令:在文件中搜索關(guān)鍵字红柱,命令會返回一個(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)庫零聚、動態(tài)庫中搜索袍暴,需要使用nm命令

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

SYTimer動態(tài)庫的符號表中搜索關(guān)鍵字

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

【第四步】

拼接KEYWORD

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

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

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

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

read通過輸入重定向或听,把file的第一行所有的內(nèi)容賦值給變量line探孝,循環(huán)體內(nèi)的命令一般包含對變量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)才會退出

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

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

坑點(diǎn)一:

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

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請輸入查找的關(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}------外部"
}

測試腳本的輸出結(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)境變量會被復(fù)制到子Shell中(用export定義的變量才是系統(tǒng)環(huán)境變量)
  • 一個(gè)Shell中的系統(tǒng)環(huán)境變量只對該Shell或者它的子Shell有效,該Shell結(jié)束時(shí)變量消失奠衔,并不能返回到父Shell
  • 不用export定義的變量只對該Shell有效谆刨,對子Shell也是無效的
  • 直接執(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 "請輸入查找的關(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}"
}

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

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

解決辦法:

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

function Find_Api() {

   if [[ ! -n "${KEYWORD}" ]]; then
       echo "請輸入查找的關(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}
}

測試腳本的輸出結(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)搜索全部格式

測試腳本的輸出結(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}

測試腳本的輸出結(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平級衡瓶。導(dǎo)致原本的*.framework輸出變成了ws.framework

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

set -x
find . -name *.framework
-------------------------
+ find . -name ws.framework
  • 不加引號的*牲证,首先會被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'
  • 加了引號十厢,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

測試腳本的輸出結(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}包颁,它會將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}

測試腳本的輸出結(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 \)

測試腳本的輸出結(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,被包裹成'"*.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

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

【第七步】

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

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

測試腳本的輸出結(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

...
  • 對于中間包含空格的目錄滔灶,原本是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)鍵字萄涯,有以下三種情況:

  • 對于源碼文件绪氛,可以直接使用grep命令
  • 對于.o.a文件涝影,需要使用nm命令查找符號表
  • 對于.framework文件枣察,也是目錄格式,需要先進(jìn)入x.framework目錄燃逻,對x進(jìn)行符號表查找
       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命令查找符號表
  • 使用popd命令叛赚,從目錄堆棧中刪除目錄
  • 如果是文件,判斷文件格式是否屬于源碼文件
  • 如果是源碼文件稽揭,通過grep命令搜索關(guān)鍵字
  • 如果非源碼文件俺附,通過nm命令查找符號表

測試腳本的輸出結(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 "請輸入查找的關(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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市障般,隨后出現(xiàn)的幾起案子调鲸,更是在濱河造成了極大的恐慌,老刑警劉巖挽荡,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藐石,死亡現(xiàn)場離奇詭異,居然都是意外死亡定拟,警方通過查閱死者的電腦和手機(jī)于微,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門逗嫡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人株依,你說我怎么就攤上這事驱证。” “怎么了恋腕?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵抹锄,是天一觀的道長。 經(jīng)常有香客問我荠藤,道長伙单,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任哈肖,我火速辦了婚禮吻育,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淤井。我一直安慰自己扫沼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布庄吼。 她就那樣靜靜地躺著缎除,像睡著了一般。 火紅的嫁衣襯著肌膚如雪总寻。 梳的紋絲不亂的頭發(fā)上器罐,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音渐行,去河邊找鬼轰坊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祟印,可吹牛的內(nèi)容都是我干的肴沫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蕴忆,長吁一口氣:“原來是場噩夢啊……” “哼颤芬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起套鹅,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤站蝠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后卓鹿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菱魔,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年吟孙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澜倦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聚蝶。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藻治,靈堂內(nèi)的尸體忽然破棺而出碘勉,到底是詐尸還是另有隱情,我是刑警寧澤栋艳,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布恰聘,位于F島的核電站,受9級特大地震影響吸占,放射性物質(zhì)發(fā)生泄漏晴叨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一矾屯、第九天 我趴在偏房一處隱蔽的房頂上張望兼蕊。 院中可真熱鬧,春花似錦件蚕、人聲如沸孙技。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牵啦。三九已至,卻和暖如春妄痪,著一層夾襖步出監(jiān)牢的瞬間哈雏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工衫生, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裳瘪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓罪针,卻偏偏與公主長得像彭羹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子泪酱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容

  • Shell(Unix Shell)是一種命令行解釋器派殷,是Unix操作系統(tǒng)下最傳統(tǒng)的人機(jī)接口。 Shell腳本是解釋...
    帥駝駝閱讀 990評論 0 5
  • Shell 初識 一西篓、程序 1愈腾、什么是程序 程序是為實(shí)現(xiàn)特定目標(biāo)或解決特定問題而用計(jì)算機(jī)語言編寫的命令序列的集合。...
    kobe_liu閱讀 379評論 0 0
  • 什么是庫岂津? 庫(Library):就是?段編譯好的?進(jìn)制代碼,加上頭?件就可以供別?使?悦即。 常?庫?件格式:.a吮成、...
    帥駝駝閱讀 554評論 0 7
  • 【腳本1】打印形狀 打印等腰三角形橱乱、直角三角形、倒直角三角形粱甫、菱形 【腳本2】截取字符串 現(xiàn)有一個(gè)字符串如下: h...
    學(xué)無止境_9b65閱讀 459評論 0 1
  • 1. 讀取用戶變量: read命令是用于從終端或者文件中讀取輸入的內(nèi)建命令泳叠,read命令讀取整行輸入,每行末尾的換...
    linux服務(wù)器開發(fā)閱讀 267評論 0 0