I want to use NSTask to simulate the Terminal to run commands. The codes as follows. It can get input in loop and return the process output.
int main(int argc, const char * argv[])
{
@autoreleasepool {
while (1) {
char str[80] = {0};
scanf("%s", str);
NSString *cmdstr = [NSString stringWithUTF8String:str];
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/sh"];
[task setArguments:[NSArray arrayWithObjects:@"-c", cmdstr, nil]];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task launch];
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", string);
}
}
My question is: when a loop is end, the running environment restore to the initialization state. For example, the default running path is /Users/apple
, and I run cd /
to change the path to /
, and then run pwd
, it return the /Users/apple
rather than the /
.
So how can I use NSTask
to simulate the Terminal completely ?
cd
and pwd
are shell built-in commands. If you execute the task
/bin/sh -c "cd /"
there is no way of getting the changed working directory back to the calling process. The same problem exists if you want to set variables MYVAR=myvalue
.
You could try to parse these lines separately and update the environment. But what about multi-line commands like
for file in *.txt
do
echo $file
done
You cannot emulate that by sending each line to separate NSTask
processes.
The only thing you could do is to start a single /bin/sh
process with NSTask
, and feed all the input lines to the standard input of that process. But then you can not use readDataToEndOfFile
to read the output, but you have to read asynchronously (using [[pipe fileHandleForReading] waitForDataInBackgroundAndNotify]
).
So in short: you can simulate the Terminal only by running a (single) shell.
ADDED: Perhaps you can use the following as a starting point for your app. (I have omitted all error checking.)
int main(int argc, const char * argv[])
{
@autoreleasepool {
// Commands are read from standard input:
NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
NSPipe *inPipe = [NSPipe new]; // pipe for shell input
NSPipe *outPipe = [NSPipe new]; // pipe for shell output
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/sh"];
[task setStandardInput:inPipe];
[task setStandardOutput:outPipe];
[task launch];
// Wait for standard input ...
[input waitForDataInBackgroundAndNotify];
// ... and wait for shell output.
[[outPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
// Wait asynchronously for standard input.
// The block is executed as soon as some data is available on standard input.
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
object:input queue:nil
usingBlock:^(NSNotification *note)
{
NSData *inData = [input availableData];
if ([inData length] == 0) {
// EOF on standard input.
[[inPipe fileHandleForWriting] closeFile];
} else {
// Read from standard input and write to shell input pipe.
[[inPipe fileHandleForWriting] writeData:inData];
// Continue waiting for standard input.
[input waitForDataInBackgroundAndNotify];
}
}];
// Wait asynchronously for shell output.
// The block is executed as soon as some data is available on the shell output pipe.
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
object:[outPipe fileHandleForReading] queue:nil
usingBlock:^(NSNotification *note)
{
// Read from shell output
NSData *outData = [[outPipe fileHandleForReading] availableData];
NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
NSLog(@"output: %@", outStr);
// Continue waiting for shell output.
[[outPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
}];
[task waitUntilExit];
}
return 0;
}