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、Arguments和Directory
例如:執(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文件的路徑- 定義
CMD和TTY變量,以供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中,指定源碼文件、framework、libs三種文件格式,找到包含main或AF關(guān)鍵字的文件列表
【步驟一】
定義變量
#!/bin/sh declare DIRECTORY="." declare SOURCE="" declare FRAMEWORK="" declare LIB="" declare KEYWORD=""
DIRECTORY:指定搜索的目錄SOURCE:是否搜索源碼文件FRAMEWORK:是否搜索frameworkLIB:是否搜索靜態(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ù)-1exit:退出當(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í)- 使用
echo將help變量輸出
【第三步】
明確
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 line與for循環(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)輸出和其他文件描述符)及其退出代碼(0到255之間的數(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本地變量,使用echo將KEYWORD進(jìn)行輸出,將結(jié)果賦值給tmpfunction 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.frameworkset -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

















