当前目录路径和NSTask

4

好的,假设我正在创建一个(Bash)终端模拟器-实际上我并没有,但在描述方面相当接近。

我已经成功地得到了(几乎)所有东西的工作,然而我面临一个简单的问题:如何保持当前目录。

我的意思是……假设用户运行 pwd 命令,我们通过 NSTask/usr/bin/env bash 执行该命令。这将输出当前应用程序所在的目录。没问题。

现在,假设用户输入 cd .. 命令。路径是否改变了?(好吧,即使只是对于特定的会话,它也发生了改变,是吗?)

因此,我想在任务终止时存储任务的 currentDirectoryPath,然后在启动另一个与 Bash 相关的任务时重新设置它。

然而,它一直得到非常相同的路径(应用程序包所在的路径)。

我错过了什么?

有任何想法可以让它工作吗?


代码

- (NSString*)exec:(NSArray *)args environment:(NSDictionary*)env action:(void (^)(NSString*))action completed:(void (^)(NSString*))completed
{
    _task                           = [NSTask new];
    _output                         = [NSPipe new];
    _error                          = [NSPipe new];
    _input                          = [NSPipe new];
    NSFileHandle* outputF           = [_output fileHandleForReading];
    NSFileHandle* errorF            = [_error fileHandleForReading];

    __block NSString* fullOutput    = @"";

    NSMutableDictionary* envs = [NSMutableDictionary dictionary];

    envs[@"PATH"] = [[APP environment] envPath];

    [_task setLaunchPath:@"/usr/bin/env"];
    if (env)
    {
        for (NSString* key in env)
        {
            envs[key] = env[key];
        }
    }
    [_task setEnvironment:envs];

    [_task setArguments:args];
    [_task setStandardOutput:_output];
    [_task setStandardError:_error];
    [_task setStandardInput:_input];

    void (^outputter)(NSFileHandle*) = ^(NSFileHandle *file){
        NSData *data = [file availableData];
        NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        action(str);

        fullOutput = [fullOutput stringByAppendingString:str];
    };

    [outputF setReadabilityHandler:outputter];
    [errorF setReadabilityHandler:outputter];

    [_task setTerminationHandler:^(NSTask* task){
        completed(fullOutput);

        dispatch_async(dispatch_get_main_queue(), ^{
            [[APP environment] setPwd:[task currentDirectoryPath]];
        });

        [task.standardOutput fileHandleForReading].readabilityHandler = nil;
        [task.standardError fileHandleForReading].readabilityHandler = nil;
        [task.standardInput fileHandleForWriting].writeabilityHandler = nil;
        [task terminate];
        task = nil;
    }];

    if (![[[APP environment] pwd] isEqualToString:@""])
        [_task setCurrentDirectoryPath:[[APP environment] pwd]];

    [_task launch];

    return @"";
}

所以,我考虑在任务终止时存储任务的currentDirectoryPath,然后在启动另一个Bash相关任务时重新设置它。这种方法似乎是有效的。你能否展示一下代码,以便我们找出问题所在? - Lawrence H
@LawrenceH 代码已上传。 :-) - Dr.Kameleon
2个回答

3
一般情况下,从外部修改另一个进程的环境和其他属性是困难的甚至不可能的。同样,通常也无法从外部查询这些属性。调试器和ps可以使用特殊权限来执行此操作。
创建有关进程的父进程在生成子进程时有机会设置初始环境和属性。
由于cd命令必须修改shell进程的状态,因此它必然是一个内置命令。该更改不会直接影响任何其他现有进程的状态。这将被后续创建的子进程继承。 NSTaskcurrentDirectoryPath属性仅在启动任务时有效。它是新进程将会继承的当前目录。它不会跟踪子进程的当前目录,因为它无法这样做。仅查询值只返回NSTask对象配置使用的值(或默认值,即创建NSTask对象的进程的当前目录)。
如果您尝试编写类似终端仿真器的东西,则需要创建一个具有长时间运行的交互式shell子进程,并在父进程和shell之间建立通信管道。不要在单独的进程中运行单个命令。相反,在管道上将命令写入交互式shell并阅读随后的输出。解释该输出可能是没有意义的,因为它可以具有一般形式且不易解析。只需直接向用户显示即可。
或者,您必须在父进程中本地解释某些命令。这将类似于shell内置命令。因此,您需要识别cd命令,并在不启动NSTask以执行它的情况下修改父进程的状态,以便新的当前目录将用于后续任务。也就是说,您可以在变量中跟踪新的当前目录,并在启动它们之前为所有后续的NSTask对象设置currentDirectoryPath,或者您可以使用-[NSFileManager changeCurrentDirectoryPath:]修改父进程的当前目录,这将自动被未来的子进程继承。

1

我相当确定在NSTask中运行cd不会改变task.currentDirectoryPath的值。您是否尝试在调度调用中设置断点,以查看该值是否实际上被正确设置?

编辑:
从您的终止处理程序中尝试执行以下操作:[[APP environment] setPwd:[[[NSProcessInfo processInfo]environment]objectForKey:@"PATH"]];


我刚想到一个有趣的点子:从shell的环境变量中获取当前目录(如果你使用/usr/bin/env,你会在其中找到你的PWD)。让我试一下,然后我会发布我的结论... :-) - Dr.Kameleon
我认为这并没有解决你原来的问题,即每次NSTask调用时环境都会重置。 - Lawrence H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接