在将 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 版本除非您提供要用于备份文件的扩展名,否则将无法工作(并将使用该扩展名备份输入文件)。