overview

日常的生活和工作中,每个人都不得不做一些重复的事情。当下火热的 AI 从某种意义上讲,就是让机器代替人去处理一些情况复杂且重复性高的事情,比如自动驾驶、人脸识别等。对技术人员来说,工作中同样有很多重复性的工作,这些事情一旦掌握之后,不断重复就变得毫无意义。
本文将结合切身的需求,展现如何运用基本的 Shell脚本,来解决工作中出现的一些实际问题。

举个栗子

金山云的编解码技术很成熟,我想使用金山的功能,但不想将其打包为 .framework,而是直接在 Xcode 工程中引入一些 .a 文件。
aFiles
在导入了对应的 .a 文件和源码之后,编译失败,提示:

1
2
3
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_KSYOfflineAuth", referenced from:
...

从错误提示上看,是我们源码的功能中使用了 KSYOfflineAuth 的类,但是没有导入对应的实现类或.a文件。

要做的事

当前已经明确了缺少实现代码的类名,而从 .a 文件的名字来看,根本无法区分出该类属于哪个 .a 文件。所以,需要使用 ar 命令,拆分出 .a 文件中所包含的所有 .o 文件。

ar 命令操作的 .a 库只能是 Non-fat 的文件,所以需要先使用 lipo 命令,为 Fat 的 .a 库 瘦身

拷贝 .a 文件所在文件夹 libs 到指定目录,并进入 libs,首先拆分 libksystreamerbase.a 来找一找目标类

1
2
3
4
// 查看 libksystreamerbase.a 的信息
$ lipo libksystreamerbase.a -info

Architectures in the fat file: libksystreamerbase.a are: armv7 i386 x86_64 arm64

从输出中可以看出 libksystreamerbase.a 是一个 fat file,并且包含我使用 iPhone 6s Plus 调试时所需要的 arm64 指令集

1
2
// 将 libksystreamerbase.a 瘦身为只有 arm64 指令集的新文件 libksystreamerbase_arm64.a
$ lipo libksystreamerbase.a -thin arm64 -output libksystreamerbase_arm64.a

此时已经准备好了单一指令集的 Non-fat libksystreamerbase_arm64.a 文件,在拆分之前,最好为 libksystreamerbase_arm64.a 文件创建一个文件夹,来存放才分出来的的文件

1
2
mkdir libksystreamerbase_dir
cd libksystreamerbase_dir

执行 ar 命令

1
2
$ ar -x ../libksystreamerbase_arm64.a
$ ls

此时,libksystreamerbase_dir 文件夹下存放的,就应该是 libksystreamerbase.a 中包含的 .o 文件了,可以查看是否存在我们在寻找的 KSYOfflineAuth

事实证明,我们的运气并不好,libksystreamerbase.a 没有要找的 .o 文件。那是不是说极限情况下,我需要拆分所有的 .a 库才能找到哪个库包含 KSYOfflineAuth

善用Shell脚本

直接怼脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#! bin/bash

THIN_LIBS=thinLibs
O_FILES=oFiles
function openLib() {
if [ $# -lt 1 ];then
echo '需要输入thin文件.'
fi

THINDIR=$1_thin_dir

mkdir -p $THIN_LIBS
mkdir -p $O_FILES

if [ $1 ]; then
cd $THIN_LIBS
lipo ../$1 -thin arm64 -output 64_$1
cd ../$O_FILES
mkdir -p $THINDIR
cd $THINDIR
ar -x ../../$THIN_LIBS/64_$1
cd ../../
echo "解压了 $1 "
fi
}


LIB_DIR=`pwd`

for file in `ls`
do
echo $file
if [ $file = 'makeFiles.sh' ];then
echo '不操作本文件'
else
openLib $file
fi
done

这只是一个及其简略的脚本,大体做了一下几件事情:

  • 定义openLib()方法,方法内为所处理的 .a 文件创建保存 .o 文件的文件夹,并瘦身拆分目标 .a 文件
  • 获取当前路径,并遍历当前目录下所有文件名
  • 每获取到一个文件名,都去调用定义好的openLib()方法

我将 .sh 命名为 makeFiles.sh,并在脚本中做了判断,不处理当前 .sh 文件。使用时,只需要将 makeFiles.sh 放到 libs 目录下,运行:

1
$ sh makeFiles.sh

控制台会持续输出日志,并很快执行完成。此时 libs 目录下会生成新创建的 oFilesthinLibs 目录,内部分别存放着所有原始 .a 文件拆分后的 .o 文件和单一指令集的 .a 文件。

在当前 libs 目录下,使用 find 命令,模糊搜索之前缺失类的名字

1
$ find . -name "*KSYOfflineAuth*"

输出

1
./libksybase.a_thin_dir/KSYOfflineAuth.o

Found it!!

搜索结果显示,目标文件在 libksybase.a_thin_dir 目录下,libksybase.a_thin_dirlibksybase.a 瘦身后拆分出来的内容目录,所以,我们只需要在工程中导入 libksybase.a 即可。

如上 Shell 脚本中,对于没有 Shell 脚本基础的人来说,只需要明确几点就很容易理解:

1
2
3
4
5
1. ` `的意思是获取其中命令的执行结果
2. ' '和" "的内容是一个字符串
3. 使用 function 来定义函数,函数调用直接使用函数名
4. if 判断右侧需要有 then,结尾需要有对应的 fi
5. Shell脚本中 $1 代表调用时传入的第一个参数

使用 Shell 脚本只需要学习一些很简单的基础概念,结合终端命令就可以运用于很多实际的场景中,这也是相比于 GUI 的 git 管理等工具而言,我更喜欢终端的原因之一。

最后

Shell脚本可以执行很多固定规则的事情,比如上文对 .a 文件的拆分,我可以一个一个进行操作,但是既耗时又无聊。使用Shell脚本可以在工作中为我们应付很多重复枯燥的事情,比如打包ipa、修改配置文件、自动化部署等等,灵活运用才是王道。

祝玩的开心~