Xcode执⾏脚本的三种⽅式
方式一
新建
Empty工程,命名mode1
创建
Target,选择Aggregate,命名RunScript
点击
RunScript,选择Build Phases,点击+,选择New Run Script Phase
名称允许重命名,这里修改为
CustomScript。文本框内可输入脚本代码
支持创建多个
Run Script Phase
方式二
新建
External Build System工程,命名mode2
和
方式一有所不同,这里可以配置Build Tool、Arguments和Directory
例如:执行一个上传
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,进行如下配置
Build Tool:配置命令Arguments:配置参数Directory:配置工作目录使用
External Build System工程,在编译阶段,还可以看到日志的输出
方式三
使用
xcconfig文件,定义变量
在
xcode_run_cmd.sh文件,使用了xcconfig中的变量
方式三可以将脚本中的关键代码和命令,在项目中使用xcconfig文件进行控制。配合方式一和.sh文件一起使用,相对更为灵活
实战解析
案例1
完成一个简单的
Shell脚本,可执行Shell命令,将运行结果或错误信息输出到终端
创建
xcode_run_cmd.sh文件,写入以下代码:声明
RunCMDToTTY函数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 }
- 判断
参数1非空,将参数1赋值给CMD变量- 判断
参数2非空,将参数2赋值给TTY变量- 判断
TTY变量为空,通过eval "tty"命令获取终端标识- 获取终端标识后,如果
TTY变量为空,输出错误提示- 判断
CMD变量非空,调用RunCommand函数,传入CMD变量。否则输出错误提示
声明
RunCommand函数declare VERBOSE_SCRIPT_LOGGING="" RunCommand() { if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then echo "♦ $@" 1>$TTY echo "-------------------------" 1>$TTY fi echo `$@ &>$TTY` return $? }
- 定义
VERBOSE_SCRIPT_LOGGING全局变量,控制是否输出所有参数,用于调试脚本- 如果
VERBOSE_SCRIPT_LOGGING变量非空,输出所有参数和分割线- 通过
echo + 反引号执行命令并输出- 显示最后命令的退出状态。
0表示没有错误,其他任何值表明有错误
声明
EchoError函数EchoError() { if [[ -n "$TTY" ]]; then echo "$@" 1>&2>$TTY else echo "$@" 1>&2 fi }
1>&2:将标准输出重定向到标准错误输出,就是以标准错误格式打印所有参数- 如果
TTY参数非空,通过终端标识输出到指定终端窗口
测试
xcode_run_cmd.sh脚本只传入命令,将结果输出在当前终端窗口
./xcode_run_cmd.sh 'ls -a' ------------------------- . .DS_Store shell .. Common Symbol xcode_run_cmd.sh
传入命令和终端标识,将结果输出到指定终端标识窗口
新开一个终端窗口,使用
tty获取终端标识
在原始窗口输入
./xcode_run_cmd.sh 'ls -a' '/dev/ttys003'命令
配合
Xcode使用搭建一个项目,将
xcode_run_cmd.sh脚本拷贝到项目根目录
创建
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脚本使用点击
Target,选择Build Phases,在Run Script中输入:/bin/sh "$SRCROOT/xcode_run_cmd.sh"
项目编译后,自动将
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
完成一个相对复杂的
Shell脚本。指定目录,指定文件格式,在文件内容中搜索关键字,最终列出包含关键字的文件列表
演示脚本功能:
使用
sh find_api.sh --help命令find_api.sh --directory <dir> 在指定目录指定文件内搜索指定关键字。 -d|--directory <dir> - 指定查找目录,默认当前所在目录 -k|--keyword <word> - 查找关键字 -s|--source - 指定查找源码文件 -f|--framework - 指定查找framework文件 -l|--lib - 指定查找libs文件 --help - prints help screen
- 可指定目录
- 可指定文件格式
- 支持长参数,例如:
--keyword- 支持短参数,例如:
-k- 可指定多个搜索关键字
原理:在源码文件中,可以直接使用
grep搜索内容,但在目标文件、静态库、动态库中,需要搜索符号表中的信息
演示执行效果:
在
.xcframework中,指定源码文件、framework、libs三种文件格式,找到包含main或AF关键字的文件列表
【步骤一】
定义变量
#!/bin/sh declare DIRECTORY="." declare SOURCE="" declare FRAMEWORK="" declare LIB="" declare KEYWORD=""
DIRECTORY:指定搜索的目录SOURCE:是否搜索源码文件FRAMEWORK:是否搜索frameworkLIB:是否搜索静态库、动态库KEYWORD:搜索关键字参数解析
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
$#:传入的参数的个数- -
gt:大于shift:使参数向右发生位移,每次调用shift时,它将所有位置上的参数-1exit:退出当前Shell进程,并返回一个退出状态。退出状态为0表示成功,退出状态为非0表示失败代码逻辑:
- 定义五个变量
- 当参数个数大于
0循环遍历参数- 每次获取
$1进行参数匹配- 命中
-d、--directory,使用shift让参数位置-1,然后再次获取$1将指定目录赋值给变量,再使用shift让参数位置-1- 命中
-k、--keyword,同理,获取$1将指定关键字赋值给变量- 命中
-s、--source,将搜索源码文件的标识设置为1,使用shift让参数位置-1- 命中
-f、--framework,同理,将搜索framework的标识设置为1- 命中
-l、--lib,同理,将搜索静态库、动态库的标识设置为1- 命中
-h、--help,调用show_usage函数,退出当前Shell进程,并返回0表示成功- 以上均未命中,将参数输出,退出当前
Shell进程,并返回1表示失败- 其中
-k、--keyword支持多个参数,这里使用换行符,将多个关键字拼接到一起
【第二步】
声明
show_usage函数function show_usage() { local help=$(cat <<EOF find_api.sh --directory <dir> 在指定目录指定文件内搜索指定关键字。 -d|--directory <dir> - 指定查找目录,默认当前所在目录 -k|--keyword <word> - 查找关键字 -s|--source - 指定查找源码文件 -f|--framework - 指定查找framework文件 -l|--lib - 指定查找libs文件 -h|--help - prints help screen EOF) echo "$help" }
EOF只是一个标识而已,可以替换成任意的合法字符EOF作为结尾的标识一定要顶格写,前面不能有任何字符EOF作为结尾的标识后面也不能有任何的字符(包括空格)EOF作为起始的标识前后的空格会被省略掉- 使用
$()包装成命令,作用与反引号一样,此处不加也行代码逻辑:
- 声明
show_usage函数,参数命中-h、--help时,输出帮助信息- 定义
help本地变量- 使用
$()包装成命令,赋值给help变量- 使用
cat <<,将带有结束标志的文档内容传递到命令的标准输入- 开始的
EOF作为起始标识- 最后的
EOF作为结尾标识- 使用
echo将help变量输出
【第三步】
明确
find_api.sh的两个核心原理:
- 在目录中找到指定格式的文件
- 在文件中搜索指定关键字
在目录中找到指定格式的文件
find命令:从指定的起始目录开始,递归地搜索其各个子目录,查找满足寻找条件的文件并对之采取相关的操作在
.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命令提供的寻找条件可以是一个用逻辑运算符not、and、or组成的复合条件or:逻辑或,在命令中用-o表示。该运算符表示只要所给的条件中有一个满足时,寻找条件就算满足and:逻辑与,在命令中用-a表示,是系统缺省的选项,表示只有当所给的条件都满足时,寻找条件才算满足not:逻辑非,在命令中用!表示。该运算符表示查找不满足所给条件的文件- 当使用很多的逻辑选项时,可以用括号把这些选项括起来。为了避免
Shell本身对括号引起误解,在括号前需要加转义字符\来去除括号的意义
-exec 命令名称 {}:对符合条件的文件执行所给的命令,而不询问用户是否需要执行该命令find ./mm.xcframework \( -name "*.a" -o -name "*.o" \) -exec echo {} \; ------------------------- ./mm.xcframework/test.o ./mm.xcframework/libAFNetworking.a
{}:表示命令的参数即为所找到的文件- 命令的末尾必须加上终结符,终结符有
;和+两种。其中;会对每一个find到的文件去执行一次cmd命令。而+让find到的文件一次性执行完cmd命令
在文件中搜索指定关键字
如果在
.h、.m、.swift文件格式中搜索,可以直接使用grep命令
grep命令:在文件中搜索关键字,命令会返回一个包含关键字的文本行在
test.m文件中,搜索@"SomeNewFunction_weak_import"grep "@\"SomeNewFunction_weak_import\"" ./mm.xcframework/test.m ------------------------- NSLog(@"SomeNewFunction_weak_import");使用
-A 1参数输出结果之后一行,使用-B 1参数输出结果之前一行grep "@\"SomeNewFunction_weak_import\"" -A 1 -B 1 ./mm.xcframework/test.m ------------------------- void SomeNewFunction_weak_import(void) { NSLog(@"SomeNewFunction_weak_import"); }使用
-i参数,忽略字符大小写的差别grep "@\"somenewfunction_weak_import\"" -A 1 -B 1 -i ./mm.xcframework/test.m ------------------------- void SomeNewFunction_weak_import(void) { NSLog(@"SomeNewFunction_weak_import"); }使用
-E参数,使用正则表达式搜索关键字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;如果在目标文件、静态库、动态库中搜索,需要使用
nm命令
nm命令:被用于显示二进制目标文件的符号表在
SYTimer动态库的符号表中搜索关键字nm -pa ./mm.xcframework/SYTimer.framework/SYTimer | grep -E "nextFireTime" ------------------------- 000057b4 t -[SYTimerBase(Private) nextFireTime] 0000000000006f08 t -[SYTimerBase(Private) nextFireTime]
【第四步】
拼接
KEYWORD无论使用
grep命令还是nm命令,在搜索关键字时都会使用正则的匹配格式,所以需要将KEYWORD按照key1|key2|key3的格式拼接在
【步骤一】中,已经将多个关键字按照回车符进行拼接,这里声明Find_Api函数,在主函数中进行二次处理
read命令:从键盘读取变量的值,通常用在Shell脚本中与用户进行交互的场合。该命令可以一次读取多个变量的值,变量和输入的值都需要使用空格隔开。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY
-a:后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符-r:屏蔽\,如果没有该选项,则\作为一个转义字符,有的话\就是个正常的字符
read通过输入重定向,把file的第一行所有的内容赋值给变量line,循环体内的命令一般包含对变量line的处理;然后循环处理file的第二行、第三行。。。一直到file的最后一行
read命令也有退出状态,当它从文件file中读到内容时,退出状态为0,循环继续进行。当read从文件中读完最后一行后,下次便没有内容可读了,此时read的退出状态为非0,所以循环才会退出
while read line与for循环的区别:
while read line是一次性将文件信息读入并按行赋值给变量line,while中使用重定向机制,文件中的所有信息都被读入并重定向给了整个while语句中的line变量
坑点一:
使用
echo输出KEYWORD,通过管道,将内容作为while read命令的标准输入function Find_Api() { if [[ ! -n "${KEYWORD}" ]]; then echo "请输入查找的关键字!" 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}------内部" done echo "${key_word}------外部" }测试脚本的输出结果:
sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd" ------------------------- cat------内部 cat|kc------内部 cat|kc|hk------内部 cat|kc|hk|kd------内部 ------外部
- 在
while循环中的打印没有任何问题,但是在循环之外打印,key_word的值莫名其妙的置空了上述问题,因为使用管道而产生
- 在大多数
Shell中(包括bash),管道的每一侧都在子Shell中运行,因此,Shell内部状态的任何更改(例如,设置变量)都仅限于管道的该段。您可以从子Shell上获得的唯一信息是它的输出(到标准输出和其他文件描述符)及其退出代码(0到255之间的数字)- 当打开一个子
Shell时,父Shell里面中的系统环境变量会被复制到子Shell中(用export定义的变量才是系统环境变量)- 一个
Shell中的系统环境变量只对该Shell或者它的子Shell有效,该Shell结束时变量消失,并不能返回到父Shell中- 不用
export定义的变量只对该Shell有效,对子Shell也是无效的- 直接执行一个脚本文件是在一个子
Shell中运行的,而在脚本前加source,则是在当前Shell环境中直接运行(不是子Shell)
坑点二:
解决子
Shell问题,需要避免管道的使用修改方案,使用
<<<,表示将右侧的字符串传递到左侧命令的标准输入function Find_Api() { if [[ ! -n "${KEYWORD}" ]]; then echo "请输入查找的关键字!" 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}" }测试脚本的输出结果:
sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd" ------------------------- catnkcnhknkdn
- 文本中间的
\n,没有被识别为换行,而是被当做\n输出了,所以while read逐行读取没有生效
解决办法:
定义
tmp本地变量,使用echo将KEYWORD进行输出,将结果赋值给tmpfunction Find_Api() { if [[ ! -n "${KEYWORD}" ]]; then echo "请输入查找的关键字!" 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} }测试脚本的输出结果:
sh find_api.sh -k "cat" -k "kc" -k "hk" -k "kd" ------------------------- cat|kc|hk|kd
- 结果符合预期,问题完美解决
【第五步】
拼接将要搜索的文件格式
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内容- 如果指定
LIB,追加LIB_EXTENSION内容- 如果最终指定的
find_name为空,默认搜索全部格式测试脚本的输出结果:
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
将结果按照
find命令的参数格式拼接:-name p1 -o -name p2 -o -name p3...坑点一:
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}测试脚本的输出结果:
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
- 因为目录中存在
ws.framework文件,和find_api.sh平级。导致原本的*.framework输出变成了ws.framework
使用
set -x命令,显示该指令及所下的参数set -x find . -name *.framework ------------------------- + find . -name ws.framework
- 不加引号的
*,首先会被bash进行扩展,所以find . -name *.framework在执行find命令前,bash先把*.framework替换成了ws.framework,然后find命令看到的参数实际上是ws.frameworkset -x find . -name "*.framework" ------------------------- + find . -name '*.framework'
- 加了引号,
bash就不去做替换了,那么find命令看到的参数就是*.framework
坑点二:
修改代码,在拼接
name变量时,前后加上\"if [[ ! -n "${need_name}" ]]; then need_name="-name \"${name}\"" else need_name="${need_name} -o -name \"${name}\"" fi测试脚本的输出结果:
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"
- 问题并没有解决,输出内容变成了
"ws.framework"这里还存在另一个问题,循环时使用的
for name in ${find_name},它会将find_name的内容以空格分割,此时*.framework已经被扩展为ws.framework
解决办法:
使用
read -a命令,指定find_name为数组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}测试脚本的输出结果:
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 \)测试脚本的输出结果:
sh find_api.sh -d ./mm.xcframework -k "main" -------------------------
- 没有输出任何结果
使用
set -x命令,显示该指令及所下的参数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命令,用于重新运算求出参数的内容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命令的参数,执行成功,输出文件列表
【第七步】
遍历文件列表,搜索关键字
for file in $(eval "find $DIRECTORY \( $need_name \)") do echo "${file}" done测试脚本的输出结果:
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,但遍历时,for循环按空格切分,导致目录被拆分为多条使用
while read命令,代替for循环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作为整体路径被输出
搜索关键字,有以下三种情况:
- 对于源码文件,可以直接使用
grep命令- 对于
.o、.a文件,需要使用nm命令查找符号表- 对于
.framework文件,也是目录格式,需要先进入x.framework目录,对x进行符号表查找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)关键字!\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)关键字!\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)关键字!\n\n\n" fi fi fi代码逻辑:
- 如果是目录,通过
basename命令获取文件名- 使用
pushd命令,将目录添加到目录堆栈顶部- 使用参数扩展去掉文件名的
.framework后缀,通过nm命令查找符号表- 使用
popd命令,从目录堆栈中删除目录- 如果是文件,判断文件格式是否属于源码文件
- 如果是源码文件,通过
grep命令搜索关键字- 如果非源码文件,通过
nm命令查找符号表测试脚本的输出结果:
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)关键字! + (instancetype)main; 在文件 ./mm.xcframework/SYTimer.xcframework/ios-arm64_i386_x86_64-simulator/SYTimer.framework/Headers/SYRunLoop.h 中找到了(main)关键字! /// 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)关键字! ...
附上完整
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 "请输入查找的关键字!" 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)关键字!\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)关键字!\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)关键字!\n\n\n" fi fi fi done <<< "${files}" echo "共扫描 \033[37;32;4m${file_count}\033[39;49;0m 个文件,发现 \033[37;32;4m${find_count}\033[39;49;0m 个文件包含(\033[37;31;4m${key_word}\033[39;49;0m)关键字!" } function show_usage() { local help=$(cat <<EOF find_api.sh --directory <dir> 在指定目录指定文件内搜索指定关键字。 -d|--directory <dir> - 指定查找目录,默认当前所在目录 -k|--keyword <word> - 查找关键字 -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

















