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
文件一起使用,相對更為靈活
實(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
文件的路徑- 定義
CMD
和TTY
變量肥荔,以供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
中如捅,指定源碼文件棍现、framework
、libs
三種文件格式镜遣,找到包含main
或AF
關(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)識- 使用
echo
將help
變量輸出
【第三步】
明確
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 line
與for
循環(huán)的區(qū)別:
while read line
是一次性將文件信息讀入并按行賦值給變量line
,while
中使用重定向機(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)輸出和其他文件描述符)及其退出代碼(0
到255
之間的數(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
本地變量酝碳,使用echo
將KEYWORD
進(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