Re: Re: Using NSTask and NSPipe to perform a shell script (SOLVED)
Re: Re: Using NSTask and NSPipe to perform a shell script (SOLVED)
- Subject: Re: Re: Using NSTask and NSPipe to perform a shell script (SOLVED)
- From: "Michael Ash" <email@hidden>
- Date: Wed, 6 Sep 2006 22:28:03 -0400
On 9/6/06, Keith Blount <email@hidden> wrote:
A very big thank you to all of those who took the time
to answer this. With the help you all gave me, I was
able to get this working. The main thing was setting
-setEnvironment: as Spencer pointed out.
The following code did the trick:
This is pretty much the right idea, but there are various specifics
that need to be corrected before this code would be robust enough to
be used in a real application. I've added specific commentary in with
the code.
- (void)test
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *latexPath = [bundle
pathForAuxiliaryExecutable:@"md2latex.sh"];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:latexPath];
[task setArguments:[NSArray
arrayWithObject:[latexPath
stringByDeletingLastPathComponent]]];
char* path = getenv("PATH");
NSString *newpath = [[NSString alloc]
initWithCString: path];
This will fail if the current PATH contains any non-ASCII characters.
While this is relatively unlikely, it's entirely possible. If you want
to avoid questions of encoding, you can just ask NSProcessInfo for the
environment in NSDictionary form.
newpath = [newpath stringByAppendingString:[@":"
stringByAppendingPathComponent:[latexPath
stringByDeletingLastPathComponent]]];
This will fail if the path to your application contains a colon. This
is not that unlikely.
I believe the same thing could be accomplished by simply adding . to
the PATH and then setting the task's current directory path to the
above. This avoids the colon problem.
[task setEnvironment:[NSDictionary
dictionaryWithObject:newpath
forKey:@"PATH"]];
NSPipe *readPipe = [NSPipe pipe];
NSFileHandle *readHandle = [readPipe
fileHandleForReading];
NSPipe *writePipe = [NSPipe pipe];
NSFileHandle *writeHandle = [writePipe
fileHandleForWriting];
[task setStandardInput: writePipe];
[task setStandardOutput: readPipe];
[task launch];
[writeHandle writeData:[NSData
dataWithContentsOfFile:@"/users/keithblount/Markdown/readme.markdown"]];
[writeHandle closeFile];
NSMutableData *data = [[NSMutableData alloc]
init];
NSData *readData;
while ((readData = [readHandle availableData])
&& [readData length]) {
[data appendData: readData];
}
Here you're writing all of the data in one shot, then you read all of
the data back in. The problem is that this can produce a deadlock.
Many processes read a little, write a little, read a little more,
write a little more, etc. This may not be the case with yours, but
it's possible, and should be guarded against. The problem is that if
the subtask writes enough data (about 4k), it will block waiting for
some data to be read. At which point it's no longer reading your data,
so *you* will block, and you have deadlock.
If you're always going to be writing a file into the subtask, you can
simplify this by simply getting a file handle to it by using
+fileHandleForReadingAtPath:, and passing that file handle as the
task's standard input. Standard input doesn't have to be a pipe.
If you will eventually be writing in-app data, you'll have to get
fancier. Unfortunately, NSFileHandle doesn't even seem to have a
method for determining whether any data can be written, or for writing
only that data which would block, which makes it hard to implement
this correctly.
One possible method would be to ask the file handles for their
fileDescriptor, then use a standard UNIX select() loop to make sure
that you're reading and writing simultaneously. Another method would
be to spawn a temporary thread to do the writing.
NSString *outputString;
outputString = [[NSString alloc]
initWithData: data
encoding: NSASCIIStringEncoding];
This will fail if the data is non-ASCII. Which encoding is correct
depends entirely on the subtask, however.
//[data
writeToFile:@"/users/keithblount/Markdown/CocoaTest.tex"
atomically:YES];
[task release];
[data release];
[outputString autorelease];
[textView setString:outputString]; // Write the
string to the text view for testing purposes
}
For the sake of the archives, this was the original
shell script (named "md2latex.sh"):
cd "%SCR_BUNDLE_PATH"
MultiMarkdown.pl|SmartyPants.pl|xsltproc -novalid
-nonet "$SCR_BUNDLE_PATH/xhtml2memoir.xslt" -
Another way to avoid farting around with paths would be to execute
this script directly using a series of NSTasks. You can use NSPipes to
get the same behavior as the | character in the script so that you
don't have to write code to manage the IO between the tasks. Then
again, that might be overcomplicating things.
Mike
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden