ios_system: iOS程序中 system() 的替代方案

Platform: iOS Build Status Twitter

在将 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.plistResources/extraCommandsDictionary.plist。 在启动时,ios_system 会加载这些字典,并启用其中定义的命令。 你需要将这两个字典添加到 Xcode 项目的 "Copy Bundle Resources" 步骤中。

每个命令都在一个框架内定义。该框架在命令被调用时加载,并在命令退出后释放。小型命令的框架位于本项目中。解释型语言的框架较大,需要单独下载:pythonluaTeX

基于网络的命令 (nslookup, dig, host, ping, telnet) 也作为一个单独的框架提供:network_ios。 将编译后的库与其它库放在一起,并将其添加到应用程序的嵌入式库中。

ios_system 框架已经成功集成到四个 shell 中:BlinkOpenTermPisthLibTerm,一个编辑器 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。 这将下载所有依赖项 (libssh2openssl) 并在 .build 目录中构建所有 ios_system XcFrameworks。

困难方式

与你的应用程序集成

基本命令

ios_system 集成到你的应用程序中的最简单方法是,只需将所有对 system() 的调用替换为对 ios_system() 的调用。 如果你需要更多控制和信息,可以使用以下函数:

更高级的控制

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)。 例如,compressuncompress 都使用相同的函数 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 是开源的;你可以以任何你想要的方式扩展它。 请记住固有的限制:

要添加命令:

常见请求的命令: 以下是经常被请求的命令列表,以及我的经验:

许可

ios_system 本身是在 修订的 BSD 许可证(3 条款 BSD 许可证)下发布的。 对于其他工具,我尽可能使用了 BSD 版本。

使用 BSD 版本会对标志及其工作方式产生影响。 例如,sed 有两个版本,BSD 版本和 GNU 版本。 它们大致具有相同的行为,但在 -i(就地编辑)上有所不同:如果您不提供扩展名,GNU 版本将覆盖该文件;BSD 版本除非您提供要用于备份文件的扩展名,否则将无法工作(并将使用该扩展名备份输入文件)。