如何在iOS中使用Swift运行命令行命令或任务?

时间:2019-06-16 11:03:02

标签: ios swift

我正在为运行iOS 12或更高版本的Swift越狱设备编程一个iOS应用。 它是一个软件包管理器,为了安装软件包,我需要运行一个dpkg -i [PACKAGE_ID] control命令。

为了实现这一点,我做了以下功能:

func task(launchPath: String, arguments: String) {
    let task = CommandLine()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe

    task.launch()
    task.waitUntilExit()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: String.Encoding.utf8)

    return output

}

当我想运行命令时,我这样调用函数:

task(launchPath: "/usr/libexec/Thunderbolt/supersling", arguments: "/usr/bin/dpkg -i" + packageID + "control")

但是它一直给我以下错误:

'CommandLine' cannot be constructed because it has no accessible initializers

我一直在互联网上搜索此错误,并且我读到您无法在iOS中运行CommandLine(),但是我知道可以运行命令。

我一直在互联网上搜索如何执行此操作,并找到了一些用于Objective-C的内容:

NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/usr/libexec/Thunderbolt/supersling"];
    [task setArguments:@[@"/usr/bin/dpkg", @"-I", packageID, @"control"]];

    NSPipe *pipe = [NSPipe pipe];
    [task setStandardOutput:pipe];

    [task launch];
    [task waitUntilExit];

    NSFileHandle *read = [pipe fileHandleForReading];
    NSData *dataRead = [read readDataToEndOfFile];
    NSString *stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding];

这和我在Objective-C中所做的完全一样。

为什么Xcode允许运行这个Objective-C代码,而Swift却不允许呢? 还有其他方法可以为Swift运行命令吗?

谢谢。

编辑:我发现Objective-C代码导入了它自己的NSTask.h头文件:

//#import <Foundation/Foundation-Structs.h>
@class NSURL, NSArray, NSDictionary;

@interface NSTask : NSObject

@property (copy) NSURL * executableURL;
@property (copy) NSArray * arguments;
@property (copy) NSDictionary * environment;
@property (copy) NSURL * currentDirectoryURL;
@property (retain) id standardInput;
@property (retain) id standardOutput;
@property (retain) id standardError;
@property (readonly) int processIdentifier;
@property (getter=isRunning,readonly) BOOL running;
@property (readonly) int terminationStatus;
@property (readonly) long long terminationReason;
@property (copy) id terminationHandler;
@property (assign) long long qualityOfService;
+(id)currentTaskDictionary;
+(id)launchedTaskWithDictionary:(id)arg1 ;
+(id)launchedTaskWithLaunchPath:(id)arg1 arguments:(id)arg2 ;
+(id)launchedTaskWithExecutableURL:(id)arg1 arguments:(id)arg2 error:(out id*)arg3 terminationHandler:(/*^block*/id)arg4 ;
+(id)allocWithZone:(NSZone*)arg1 ;
-(void)waitUntilExit;
-(NSURL *)executableURL;
-(id)currentDirectoryPath;
-(void)setArguments:(NSArray *)arg1 ;
-(void)setCurrentDirectoryPath:(id)arg1 ;
-(id)launchPath;
-(void)setLaunchPath:(id)arg1 ;
-(int)terminationStatus;
-(long long)terminationReason;
-(void)launch;
-(BOOL)launchAndReturnError:(id*)arg1 ;
-(void)setCurrentDirectoryURL:(NSURL *)arg1 ;
-(NSURL *)currentDirectoryURL;
-(void)setExecutableURL:(NSURL *)arg1 ;
-(void)interrupt;
-(long long)suspendCount;
-(void)setStandardInput:(id)arg1 ;
-(void)setStandardOutput:(id)arg1 ;
-(void)setStandardError:(id)arg1 ;
-(id)standardInput;
-(id)standardOutput;
-(id)standardError;
-(id)init;
-(NSDictionary *)environment;
-(BOOL)isRunning;
-(BOOL)suspend;
-(BOOL)resume;
-(void)setEnvironment:(NSDictionary *)arg1 ;
-(void)setQualityOfService:(long long)arg1 ;
-(void)setTerminationHandler:(id)arg1 ;
-(int)processIdentifier;
-(id)terminationHandler;
-(long long)qualityOfService;
-(void)terminate;
-(NSArray *)arguments;
@end

我可以在Swift中使用它吗?如果可能的话,我该怎么办?

2 个答案:

答案 0 :(得分:1)

好的,所以基本上我自己搞清楚了如何做到这一点,所以我将发布此答案,以帮助可能遇到相同问题的人。

注意,这仅适用于越狱设备

这可在非越狱和越狱的iOS设备上使用

我能找到的最好的方法是使用custom Objective-C header file创建对象NSTask及其所需的一切。

然后,为了在Swift中使用此代码,您需要创建一个Bridging-Header,在其中需要导入NSTask.h才能使其在Swift中公开并被可以在您的Swift代码中使用它。

完成此操作后,只要要运行任务,就在代码中使用以下功能:

func task(launchPath: String, arguments: String...) -> NSString {
    let task = NSTask.init()
    task?.setLaunchPath(launchPath)
    task?.arguments = arguments

    // Create a Pipe and make the task
    // put all the output there
    let pipe = Pipe()
    task?.standardOutput = pipe

    // Launch the task
    task?.launch()
    task?.waitUntilExit()

    // Get the data
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)

    return output!
}

并这样称呼它:

task(launchPath: "/usr/bin/dpkg", arguments: "-i", packageID, "control")

这还将返回该值,因此您甚至可以执行以下操作来显示它:

print(task(launchPath: "/usr/bin/echo", arguments: "Hello, World!"))

将打印:

~> Hello, World!

希望这可以解决问题。

编辑:我发现,当您使用自定义NSTask对象时,即使在非越狱设备上,也可以运行该任务。在iPad Mini 2( iOS 12.1〜>越狱)和iPhone Xr( iOS 12.2〜>未越狱)上进行了测试。

注意:尽管这也适用于非越狱设备,但您的​​应用将在AppStore上被拒绝,如@ClausJørgensen所说:

您正在使用私有API,因此将在App Store上被拒绝。此外,Xcode 11具有一些新功能,这些功能会在使用某些专用API时触发构建失败。

我只建议将此方法用于不会上载到App Store的应用程序,否则,请尝试在不使用命令的情况下实现所需的功能,肯定会有其他方法。

编辑:要使其正常运行并避免抛出NSInternalInconsistencyException,您需要将launchPath设置为可执行文件的完整路径,而不仅仅是包含可执行文件的目录。

您还需要设置所有命令参数,并用 逗号 分隔。

答案 1 :(得分:0)

帮助!当我在iphone7(ios13.1〜>未越狱)中进行测试时,我已将命令文件拖到xcode项目中,并获得如下文件路径:

let commandPath = Bundle.main.path(forResource: "command", ofType: nil)! 

当我像这样在iPhone上跑步时:

task(launchPath: commandPath, arguments:"")

我得到了:

'NSInvalidArgumentException', reason: 'launch path not accessible'

如何获取命令文件的正确路径?