I have a PHP script which has mutliple sleep() commands. I would like to execute it in my application with NSTask. My script looks like this:
echo "first\n"; sleep(1); echo "second\n"; sleep(1); echo "third\n";
I can execute my task asynchronously using notifications:
- (void)awakeFromNib {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/php"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-r", #"echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";", nil];
[task setArguments: arguments];
NSPipe *p = [NSPipe pipe];
[task setStandardOutput:p];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskExited:) name:NSTaskDidTerminateNotification object:task];
[task launch];
}
- (void)taskExited:(NSNotification *)notif {
NSTask *task = [notif object];
NSData *data = [[[task standardOutput] fileHandleForReading] readDataToEndOfFile];
NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#",str);
}
My output is (after 2 seconds, of course):
2011-08-03 20:45:19.474 MyApp[3737:903] first
second
third
My question is: how can I get theese three words immediately after they are printed?
You can use NSFileHandle's waitForDataInBackgroundAndNotify method to receive a notification when the script writes data to its output. This will only work, however, if the interpreter sends the strings immediately. If it buffers output, you will get a single notification after the task exits.
- (void)awakeFromNib {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/php"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-r", #"echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";", nil];
[task setArguments: arguments];
NSPipe *p = [NSPipe pipe];
[task setStandardOutput:p];
NSFileHandle *fh = [p fileHandleForReading];
[fh waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedData:) name:NSFileHandleDataAvailableNotification object:fh];
[task launch];
}
- (void)receivedData:(NSNotification *)notif {
NSFileHandle *fh = [notif object];
NSData *data = [fh availableData];
if (data.length > 0) { // if data is found, re-register for more data (and print)
[fh waitForDataInBackgroundAndNotify];
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#" ,str);
}
}
For reference, here is ughoavgfhw's answer in swift.
override func awakeFromNib() {
// Setup the task
let task = NSTask()
task.launchPath = "/usr/bin/php"
task.arguments = ["-r", "echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";"]
// Pipe the standard out to an NSPipe, and set it to notify us when it gets data
let pipe = NSPipe()
task.standardOutput = pipe
let fh = pipe.fileHandleForReading
fh.waitForDataInBackgroundAndNotify()
// Set up the observer function
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "receivedData:", name: NSFileHandleDataAvailableNotification, object: nil)
// You can also set a function to fire after the task terminates
task.terminationHandler = {task -> Void in
// Handle the task ending here
}
task.launch()
}
func receivedData(notif : NSNotification) {
// Unpack the FileHandle from the notification
let fh:NSFileHandle = notif.object as NSFileHandle
// Get the data from the FileHandle
let data = fh.availableData
// Only deal with the data if it actually exists
if data.length > 1 {
// Since we just got the notification from fh, we must tell it to notify us again when it gets more data
fh.waitForDataInBackgroundAndNotify()
// Convert the data into a string
let string = NSString(data: data, encoding: NSASCIIStringEncoding)
println(string!)
}
}
This construct will be necessary if your task produces lots of output into the pipe. Simply calling pipe.fileHandleForReading.readDataToEndOfFile() will not work because the task is waiting for the pipe to be emptied so it can write more while your program is waiting for the end of the data. Thus, your program will hang. This notification and observer construct allows the pipe to be read asynchronously and thus prevents the aforementioned stalemate.
Related
From the Terminal, I can call all of the Salesforce DX CLI commands and functions. For example, "sfdx force:doc:commands:list" will show all the commands. BUT from the NSTask code (below) on a mac, I cannot call the Salesforce DX CLI at all. The terminationStatus flag is true. Other commands like "ls" work fine. Is this because of paths? permissions? Thanks in advance...
ptr portcommandlineinterface(char *thecommand)
{
ptr theptr;
NSTask *task;
NSPipe *pipe;
NSData *data;
NSFileHandle *file;
NSArray *arguments;
int thesize, status;
const char *junkptr;
NSString *string, *tempstring;
tempstring = [NSString stringWithUTF8String:thecommand];
if (tempstring == 0) return(0);
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
[task setCurrentDirectoryPath: #"~"];
arguments = [NSArray arrayWithObjects: #"-c", tempstring, nil];
[task setArguments: arguments];
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
file = [pipe fileHandleForReading];
[task launch];
[task waitUntilExit];
status = [task terminationStatus];
if (status) return(0);
data = [file readDataToEndOfFile];
string = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding];
junkptr = [string UTF8String];
thesize = cstrlen((char *)junkptr);
theptr = portnewptr(thesize + 10);
cstrcpy((char *)junkptr, theptr);
return(theptr);
}
Ok, so I know you can make an NSTask to run command line tools with Objective-C:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/gdb"];
[task launch];
I'm just wondering if there's a way to communicate with interactive command line tools such a as gdb. This would involve giving the command inputs based on user interaction (like run, kill or quit with gdb) and then reacting based on the information it outputs.
You can use NSTask's setStandardInput:, setStandardOutput: and setStandardError: selectors in conjunction with NSPipe instances to communicate with the launched program.
For example, to read the task's output:
task = [[NSTask alloc] init];
[task setStandardOutput: [NSPipe pipe]];
[task setStandardError: [task standardOutput]]; // Get standard error output too
[task setLaunchPath: #"/usr/bin/gdb"];
[task launch];
You can then obtain an NSFileHandle instance that you can use to read the task's output with:
NSFileHandle *readFromMe = [[task standardOutput] fileHandleForReading];
To set up a pipe for sending commands to gdb, you would add
[task setStandardInput: [NSPipe pipe]];
before you launch the task. Then you get the NSFileHandle with
NSFileHandle *writeToMe = [[task standardInput] fileHandleForWriting];
Use setStandardInput: and setStandardOutput: methods of NSTaks class.
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/gdb"];
NSPipe *outputpipe=[[NSPipe alloc]init];
NSPipe *errorpipe=[[NSPipe alloc]init];
NSFileHandle *output,*error;
[task setArguments: arguments];
[task setStandardOutput:outputpipe];
[task setStandardError:errorpipe];
NSLog(#"%#",arguments);
output=[outputpipe fileHandleForReading];
error=[errorpipe fileHandleForReading];
[task launch];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedData:) name: NSFileHandleReadCompletionNotification object:output];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedError:) name: NSFileHandleReadCompletionNotification object:error];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(TaskCompletion:) name: NSTaskDidTerminateNotification object:task];
//[input writeData:[NSMutableData initWithString:#"test"]];
[output readInBackgroundAndNotify];
[error readInBackgroundAndNotify];
[task waitUntilExit];
[outputpipe release];
[errorpipe release];
[task release];
-(void) receivedData:(NSNotification*) rec_not {
NSFileHandle *out=[[task standardOutput] fileHandleForReading];
NSData *dataOutput=[[rec_not userInfo] objectForKey:NSFileHandleNotificationDataItem];
if( !dataOutput)
NSLog(#">>>>>>>>>>>>>>Empty Data");
NSString *strfromdata=[[NSString alloc] initWithData:dataOutput encoding:NSUTF8StringEncoding];
[out readInBackgroundAndNotify];
[strfromdata release];
}
/* Called when there is some data in the error pipe */
-(void) receivedError:(NSNotification*) rec_not {
NSFileHandle *err=[[task standardError] fileHandleForReading];
NSData *dataOutput=[[rec_not userInfo] objectForKey:NSFileHandleNotificationDataItem];
if( !dataOutput)
NSLog(#">>>>>>>>>>>>>>Empty Data");
else {
NSString *strfromdata=[[NSString alloc] initWithData:dataOutput encoding:NSUTF8StringEncoding];
[strfromdata release];
}
[err readInBackgroundAndNotify];
}
/* Called when the task is complete */
-(void) TaskCompletion :(NSNotification*) rec_not {
NSLog(#"task ended");
}
I have an OS X App, it makes a call to shell script using this:
#implementation ViewController {
NSTask *task;
NSPipe *pipe;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self runCommand:[[NSBundle mainBundle] pathForResource:#"Test" ofType:#"command"] arguments:nil];
}
- (void)runCommand:(NSString *)cmd arguments:(NSArray *)args {
if (task)
{
[task interrupt];
}
else
{
task = [[NSTask alloc] init];
[task setLaunchPath:cmd];
[task setArguments:args];
pipe = [[NSPipe alloc] init];
[task setStandardOutput:pipe];
NSFileHandle* fh = [pipe fileHandleForReading];
[task launch];
[fh readInBackgroundAndNotify];
}
}
#end
The shell script Test.command has output text which will be displayed in Terminal window when you execute it in Terminal, but is there a way to retrieve this output in my OS X App and display it into a text view?
You need to observe the notification event fired by the task. The order in which you register the observer and launch the command is also important. Try something like:
{
task = [[NSTask alloc] init];
[task setLaunchPath:cmd];
[task setArguments:args];
pipe = [[NSPipe alloc] init];
[task setStandardOutput:pipe];
// Observe events before launching the task, otherwise the execution
// might end before we get the chance to register the observer.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didCompleteReadingFileHandle:)
name:NSFileHandleReadCompletionNotification
object:[[task standardOutput] fileHandleForReading]];
// Make it start reading before launching.
[[pipe fileHandleForReading] readInBackgroundAndNotify];
// Launch it.
[task launch];
}
- (void)didCompleteReadingFileHandle:(NSNotification *)notification
{
NSData *data = [[notification userInfo]
objectForKey:NSFileHandleNotificationDataItem];
// Do something with data...
}
I am very new to develop mac osx application using xcode. I am trying to get all running application list with their memory usage.
Can any body help me in this case.
Please help me.
Thanks in advance.
It will help you to get list of running application :
for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications]) {
NSLog(#"%#",[app localizedName]);
}
You may get the cpu usage as:
- (NSString*) get_process_usage:(int) pid
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/ps"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-O",#"%cpu",#"-p",[NSString stringWithFormat:#"%d",pid], nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSString* temp = [string stringByReplacingOccurrencesOfString:#"PID %CPU TT STAT TIME COMMAND" withString:#""];
NSMutableArray* arr =(NSMutableArray*) [temp componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[arr removeObject:#""];
[string release];
[task release];
if([arr count]>=2)
return [arr objectAtIndex:1];
else
return #"unknown";
}
Ok here is my code..
The return of tid is usually about 2-3 off of the actual pid. It is driving me crazy trying to figure out why it will not give me the exact pid.
So when I try to kill the process, it will not kill, I suppose i could write one to do ps -u | grep ssh vars specific to this process, but i shouldn't have too. Any ideas?
EDIT here is the actual code pasted
- (void)startService {
NSString *temp = #"-D 8080 user#127.0.0.1 -N";
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/ssh"];
[task setArguments: [temp componentsSeparatedByString:#" "]];
NSPipe *input = [NSPipe pipe];
[task setStandardInput: input];
[task launch];
int tid = [task processIdentifier];
NSLog(#"Starting task: %i", tid);
}
- (void)stopService {
if ([self isRunning]) {
int tid;
tid = [task processIdentifier];
running = false;
[task terminate];
NSString *killTask = [NSString stringWithFormat:#"/bin/kill -KILL %i", tid];
//NSLog(#"Attepting to KILL: %i", tid);
system([killTask cStringUsingEncoding:NSASCIIStringEncoding]);
//[task ];
}
}