Saturday, January 29, 2011

NSTask again

I have a bit more to explore about NSTask (previous post here). There is some discussion on SO about the difference between an NSTask and an NSThread (here).

The script task.py uses the #! thing and is set to be executable (by the user only). The code looks for an int in argv and a second arg that tells whether to examine the environment variable dictionary.


#! /usr/bin/python
import sys, os

try:
n = int(sys.argv[1])
except:
n = 2
print 'woof ' * n
if len(sys.argv) == 3:
D = os.environ
k = 'MYKEY'
if k in D: print k, D[k]
else: print k, 'not present'


The first attempt just launches the task without setting it up first.

The second example does the setup. The path p2 (to Python directly) isn't used in these versions but I left it in. For a while, I was getting errors when I tried to setup the task and then launch it unless I fed it to Python first and gave the script path as an argument. Then, the errors just stopped. I can't recreate the problem. This kind of bug that "just disappeared" happened with the environment dictionary as well. No clue as to why.


from Foundation import *

p1 = NSHomeDirectory() + '/Desktop/task.py'
p2 = '/usr/bin/python'

def f1():
t = NSTask.launchedTaskWithLaunchPath_arguments_(
p1, [str(1)])

def doit(t):
t.launch()
t.waitUntilExit()

def f2():
t = NSTask.alloc().init()
t.setLaunchPath_(p1)
doit(t)

f1()
f2()


Of note here is that the arguments must be in a list or array, and that the int argment must be converted to a string. The error if not is weird (but at least we're pointed to the correct line):


2011-01-29 03:18:00.109 Python[19298:60f] -[OC_PythonNumber fileSystemRepresentation]: unrecognized selector sent to instance 0x101a6d950
Traceback (most recent call last):
File "parent.py", line 48, in <module>
f()
File "parent.py", line 15, in f1
p1, [1])
ValueError: NSInvalidArgumentException - -[OC_PythonNumber fileSystemRepresentation]: unrecognized selector sent to instance 0x101a6d950


Otherwise we get this:


> python parent.py 
woof
woof woof


Next, we instantiate the task and then set its arguments and environment, having modified the dict to have an extra key/value pair:


D = NSProcessInfo.processInfo().environment()
D['MYKEY'] = "MYVALUE"

def f3():
t = NSTask.alloc().init()
t.setLaunchPath_(p2)
n = str(3)
t.setArguments_([p1,n,'withenviron'])
t.setEnvironment_(D)
print 'MYKEY', t.environment()['MYKEY']
doit(t)

f3()



> python parent.py 
MYKEY MYVALUE
woof woof woof
MYKEY MYVALUE


The ultimate line of output is from the task and shows that we did get the modified environment.

And in the last example, we use an NSFileHandle and redirect standard output. This could be useful in the case where you don't have full control over the task that is being executed. I found that NSFileHandle.fileHandleForWritingAtPath_ apparently requires the file to already exist.


p3 = NSHomeDirectory() + '/Desktop/results.txt'

def f4():
t = NSTask.alloc().init()
t.setLaunchPath_(p1)
n = NSNumber.numberWithInt_(4).stringValue()
t.setArguments_([n])
# file must exist or this fails!
f = NSFileHandle.fileHandleForWritingAtPath_(p3)
t.setStandardOutput_(f)
doit()
data = f.readToEndOfFileInBackgroundAndNotify()
print data

f4()



> python parent.py 
None
> cat results.txt
woof woof woof woof


The redirect is good, but I couldn't get the file read to work. I tried before and after launch, and I tried re-grabbing the file handle, and I also tried readDataToEndOfFile. Not sure what's up with that yet.

[UPDATE: Perhaps the answer has something to do with NSPipe (here). ]