在将 Unix 工具移植到 iOS (vim, TeX, python...) 时,有时源代码会执行系统命令,使用 system() 调用。 这些调用在编译时会被拒绝,并显示错误:error: 'system' is unavailable: not available on iOS。
本项目提供了一个 system() 的直接替代方案。只需在头文件的开头添加以下几行代码:
extern int ios_system(char* cmd);
#define system ios_system
链接 ios_system.framework,你的 system() 调用将由该框架处理。
可用命令: shell 命令 (ls, cp, rm...)、归档命令 (curl, scp, sftp, tar, gzip, compress...) 以及一些解释型语言 (python, lua, TeX)。 如果用这些解释型语言编写的脚本位于 $PATH 中,也会被执行。
可用命令定义在两个字典中,分别是 Resources/commandDictionary.plist 和 Resources/extraCommandsDictionary.plist。 在启动时,ios_system 会加载这些字典,并启用其中定义的命令。 你需要将这两个字典添加到 Xcode 项目的 "Copy Bundle Resources" 步骤中。
每个命令都在一个框架内定义。该框架在命令被调用时加载,并在命令退出后释放。小型命令的框架位于本项目中。解释型语言的框架较大,需要单独下载:python、lua 和 TeX。
基于网络的命令 (nslookup, dig, host, ping, telnet) 也作为一个单独的框架提供:network_ios。 将编译后的库与其它库放在一起,并将其添加到应用程序的嵌入式库中。
ios_system 框架已经成功集成到四个 shell 中:Blink、OpenTerm、Pisth 和 LibTerm,一个编辑器 iVim 和一个 TeX 写作应用 TeXable。 每次都提供了类似 Unix 的外观和感觉 (很大程度上是感觉)。
问题: 在 iOS 中,你不能写入 ~ 目录,只能写入 ~/Documents/、~/Library/ 和 ~/tmp。 大多数 Unix 程序都假定配置文件位于 $HOME 中。 因此,你要么将 $HOME 重新定义为 ~/Documents/,要么将配置变量(使用 setenv)设置为其他位置。 这在 initializeEnvironment() 函数中完成。
这是我所拥有的。
setenv PATH = $PATH:~/Library/bin:~/Documents/bin
setenv PYTHONHOME = $HOME/Library/
setenv SSH_HOME = $HOME/Documents/
setenv CURL_HOME = $HOME/Documents/
setenv HGRCPATH = $HOME/Documents/.hgrc/
setenv SSL_CERT_FILE = $HOME/Documents/cacert.pem
你的结果可能会有所不同。 请注意,iOS 已经定义了 $HOME 和 $PATH。
简单方式: (Xcode 12 及更高版本) ios_system 以一组二进制框架的形式提供。 将本项目添加为 "Swift Package dependency",并根据需要链接和嵌入框架。
半困难方式
输入 swift run --package-path xcfs build。 这将下载所有依赖项 (libssh2 和 openssl) 并在 .build 目录中构建所有 ios_system XcFrameworks。
困难方式
ios_system.xcodeproj 并点击构建。 这将创建 ios_system 框架,可以将其包含在你自己的项目中。files、tar、curl、awk、shell、text、ssh_cmd。 这将创建相应的框架。xcodebuild -project ios_system.xcodeproj -alltargets -sdk iphoneos -configuration Release -quiet 来构建所有框架。ios_system 框架才能编译。ios_system.framework 框架链接。tar,则嵌入 libtar.dylib;如果你需要 cp、rm、mv,则嵌入 libfiles.dylib...)。Resources/commandDictionary.plist 和 Resources/extraCommandsDictionary.plist 添加到 Xcode 项目的 "Copy Bundle Resources" 步骤中。将 ios_system 集成到你的应用程序中的最简单方法是,只需将所有对 system() 的调用替换为对 ios_system() 的调用。 如果你需要更多控制和信息,可以使用以下函数:
initializeEnvironment() 将环境变量设置为合理的默认值。inputCmd 是 ios_system 中定义的命令之一,则 ios_executable(char* inputCmd) 返回 true。NSArray* commandsAsArray() 返回一个包含所有可用命令的数组,如果需要为用户提供帮助。NSString* commandsAsString() 与上面相同,但返回的是一个 NSString*。NSString* getoptString(NSString* command) 返回一个包含给定命令所有接受的标志的字符串 (例如,"rm" 的 "dfiPRrvW")。如果标志不能与其他标志组合使用,则字母后跟 ":"。NSString* operatesOn(NSString* command) 告诉你这个命令期望什么作为参数,这样你就可以相应地自动完成。 返回值为 "file"、"directory" 或 "no"。 例如,"cd" 返回 "directory"。int ios_setMiniRoot(NSString* mRoot) 允许你设置沙盒目录,这样用户就不会接触到沙盒之外的文件。 参数是目录的路径。 不能 cd 到此目录之上的目录。 如果成功,则返回 1;如果失败,则返回 0。FILE* ios_popen(const char* inputCmd, const char* type) 在当前命令和 inputCmd 之间打开一个管道 (popen 的直接替代品)。replaceCommand: replaceCommand(NSString* commandName, int (*newFunction)(int argc, char *argv[]), bool allOccurences) 允许你用你自己的实现替换现有的命令实现,或者添加新的命令,而无需编辑源代码。
示例用法: replaceCommand(@"ls", gnu_ls_main, true);: 将所有对 ls 的调用替换为对 gnu_ls_main 的调用。 最后一个参数指示你是只希望替换与 ls 关联的函数 (如果为 false),还是替换所有先前使用与 ls 关联的函数的命令 (如果为 true)。 例如,compress 和 uncompress 都使用相同的函数 compress_main(实际行为取决于 argv[0])。 只有你自己知道你的替换函数是否处理这两个角色,或者只处理其中一个。
如果该命令尚不存在,则你的命令将被简单地添加到列表中。
addCommandList: NSError* addCommandList(NSString* fileLocation) 一次加载多个命令,并将它们添加到现有命令列表中。 fileLocation 指向一个 plist 文件,其语法与 Resources/extraCommandsDictionary.plist 相同:键是命令名称,后跟一个包含 4 个字符串的数组:框架名称、要调用的函数名称、选项列表(采用 getopt() 格式)以及命令期望的参数(文件、目录、无)。 后两个可用于自动完成。 如果你的命令在你的主程序中定义 (相当于 dlsym() 的 RTLD_MAIN_ONLY 选项),则框架名称可以是 MAIN;如果在 ios_system.framework 中定义 (相当于 RTLD_SELF),则框架名称可以是 SELF。
例子
<key>rlogin</key>
<array>
<string>network_ios.framework/network_ios</string>
<string>rlogin_main</string>
<string>468EKLNS:X:acde:fFk:l:n:rs:uxy</string>
<string>no</string>
</array>
ios_execv(const char path, char const argv[]): 使用参数 argv 执行 argv[0] 中的命令 (它不使用 path)。 它不是 execv 的直接替代品,因为它不会终止当前进程。 通常在 fork() 之后调用 execv,而 execv 终止子进程。 这在 iOS 中是不可能的。 如果在 execv 之前调用了 dup2 来设置 stdin 和 stdout,则 ios_execv 会尝试做正确的事情,并将这些流传递给 execv 启动的进程。
ios_execve 也存在,并存储环境。
ios_system 是开源的;你可以以任何你想要的方式扩展它。 请记住固有的限制:
traceroute) 是不可能的。要添加命令:
main() 函数更改为 command_main()。ios_error.h。ios_system.framework 链接;这将使用 ios_system 版本替换大多数函数调用 (exit、warn、err、errx、warnx、printf、write...)isatty() 的调用替换为对 ios_isatty() 的调用。ios_system 中,标准输出必须进入 thread_stout。 libc_replacement.c 拦截了大多数输出函数,但并非全部。thread_stdin。__thread 将所有全局变量设为线程本地,确保使用 static 标记局部变量。fork、exec、system、popen、isExecutableFileAtPath、access... (其中一些在编译时失败,另一些在运行时静默失败)。Resources/extraCommandsDictionary.plist 以添加你的命令,然后运行。常见请求的命令: 以下是经常被请求的命令列表,以及我的经验:
ping, nslookup, telnet: 现在在 network_ios 包中提供。traceroute 和大多数网络分析工具:需要 root 权限,因此在沙盒中不可能。unzip: 使用 tar -xz。sh、bash、zsh:即使没有沙盒/API 限制,shell 也很难编译。 它们也倾向于占用大量内存,这是一种有限的资产。git: WorkingCopy 做得非常好,你可以将目录传输到你的应用程序,然后再传输回 WorkingCopy。 也很难编译。ios_system 本身是在 修订的 BSD 许可证(3 条款 BSD 许可证)下发布的。 对于其他工具,我尽可能使用了 BSD 版本。
使用 BSD 版本会对标志及其工作方式产生影响。 例如,sed 有两个版本,BSD 版本和 GNU 版本。 它们大致具有相同的行为,但在 -i(就地编辑)上有所不同:如果您不提供扩展名,GNU 版本将覆盖该文件;BSD 版本除非您提供要用于备份文件的扩展名,否则将无法工作(并将使用该扩展名备份输入文件)。