1、命令行工具的本质

命令行工具的本质就是一个可执行文件,跟App差不多。

2、制作命令行工具

2.1、创建iOS项目

创建一个iOS项目TestCL,去掉UI相关的文件和代码,main函数:

  1. int main(int argc, char * argv[]) {
  2. @autoreleasepool {
  3. printf("TestCL--------\n");
  4. }
  5. return 0;
  6. }

2.2、设置Release模式

Edit Scheme - Run - Build Configuration - Release

2.3、添加可执行文件

3.png
将编译后生成的可执行文件复制到iPhone的usr/bin目录下,在iPhone终端输入:
$ TestCL
会提示权限不足:

  1. -sh: /usr/bin/TestCL: Permission denied

需要给可执行文件赋予“可执行的权限”
$ chmod +x /usr/bin/TestCL
再次运行:# TestCL

  1. TestCL--------

3、判断Mach-O文件类型

Mach-O文件大致可分为通用(FAT)二进制文件、64bit架构文件、非64bit架构文件,在 xnu源码 中已经定义了对应的魔数:

  1. #define FAT_MAGIC 0xcafebabe
  2. #define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */

xnu/EXTERNAL_HEADERS/mach-o/fat.h

  1. #define MH_MAGIC 0xfeedface /* the mach magic number */
  2. #define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
  1. /* Constant for the magic field of the mach_header_64 (64-bit architectures) */
  2. #define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
  3. #define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

xnu/EXTERNAL_HEADERS/mach-o/loader.h

以今日头条为例,判断它的可执行文件的类型:

  1. #import <UIKit/UIKit.h>
  2. #import <mach-o/fat.h>
  3. #import <mach-o/loader.h>
  4. int main(int argc, char * argv[]) {
  5. @autoreleasepool {
  6. // 今日头条可执行文件地址
  7. NSString *path = @"/var/containers/Bundle/Application/CCB2D157-22E3-49EB-9914-582673D42676/News.app/News";
  8. NSFileHandle* handle = [NSFileHandle fileHandleForReadingAtPath:path];
  9. int length = sizeof(uint32_t);
  10. // 读取前面的4个字节(用于标识文件类型)
  11. NSData *magicData = [handle readDataOfLength:length];
  12. // 把magicData里4个字节的数据存放到magicNumber中
  13. uint32_t magicNumber;
  14. [magicData getBytes:&magicNumber length:length];
  15. // 判断文件类型(判断两个条件是为了兼容大小端)
  16. if (magicNumber == FAT_MAGIC || magicNumber == FAT_CIGAM) {
  17. printf("FAT文件\n");
  18. } else if (magicNumber == MH_MAGIC || magicNumber == MH_CIGAM) {
  19. printf("非64bit架构文件\n");
  20. } else if (magicNumber == MH_MAGIC_64 || magicNumber == MH_CIGAM_64) {
  21. printf("64bit架构文件\n");
  22. }else {
  23. printf("读取失败\n");
  24. }
  25. printf("magicNumber = 0x%x\n", magicNumber);
  26. [handle closeFile];
  27. }
  28. return 0;
  29. }

将编译后的可执行文件复制到iPhone的usr/bin目录下,在iPhone终端输入:
$ TestCL

  1. 64bit架构文件
  2. magicNumber = 0xfeedfacf

4、参数

命令行工具可以接受参数,通过接收不同的参数进行不同的操作,命令行的参数也就是mian函数的参数:

  1. int main(int argc, char * argv[]) {
  2. @autoreleasepool {
  3. }
  4. return 0;
  5. }

agrc:参数的个数 argv:存放参数的数组 argv[0]:当前可执行文件的路径

例如接收参数 -x 执行功能a,接收参数 -y 执行功能b:

  1. int main(int argc, char * argv[]) {
  2. @autoreleasepool {
  3. if (argc == 1) { // 只有一个参数代表只有可执行文件路径
  4. return 0;
  5. }
  6. if (strcmp(argv[1], "-x") == 0) {
  7. printf("执行功能a");
  8. return 0;
  9. }
  10. if (strcmp(argv[1], "-y") == 0) {
  11. printf("执行功能b");
  12. return 0;
  13. }
  14. }
  15. return 0;
  16. }

5、权限

在使用命令行工具时,可能会遇到权限不足的问题,可以通过ldid工具来查看可执行文件的权限信息:
$ ldid -e TestCL > TestCL.entitlements

:代表将内容覆盖文件

:代表将内容追加到文件

1.png

TestCL.entitlements文件的本质就是一个plist文件

将权限签到可执行文件:
$ ldid -STestCL.entitlements TestCL
一般情况下,可以找一个合适的可执行文件,导出它的权限信息,然后签到自定义的命令行工具中,比如导出SpringBoard的权限:
$ ldid -e SpringBoard > SpringBoard.entitlements
2.png

SpringBoard可执行文件路径:System/Library/CoreServices/SpringBoard.app/SpringBoard

将SpringBoard的权限信息签到TestCL中:
$ ldid -SSpringBoard.entitlements TestCL
这样TestCL就拥有了和SpringBoard一样的权限了。