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...
}
Related
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");
}
In my NSApplication I've a button. When I click it, I want to launch/start a daemon/agent. How can I proceed? I've already tried:
- (void) runAgent {
NSTask *task = [[NSTask alloc] init];
task.launchPath = #"launchctl load /System/Library/LaunchAgents/com.mycompany.myagent.plist";
[task setLaunchPath:#"/bin/sh"];
[task launch];
}
with no success...
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/launchctl"];
[task setArguments:[NSArray arrayWithObjects:#"load",#"/System/Library/LaunchAgents/com.mycompany.myagent.plist", nil]];
[task launch];
I would like to execute a command with NSTask, and be able to see the progress in a modal window. For example if I execute 'ls -R /' i would like to see the chunks appearing in a NSTextView.
I came up with the following, and everything works fine, except the update part. The task get executed (with the spinning beachbal) and when it is finished i see the result appear in the textview.
#interface ICA_RunWindowController ()
#property (strong) IBOutlet NSTextView* textResult;
#property (strong) IBOutlet NSButton* buttonAbort;
#property (strong) IBOutlet NSButton* buttonOK;
- (IBAction) doOK:(id) sender;
- (IBAction) doAbort:(id) sender;
#end
#implementation ICA_RunWindowController {
NSTask * executionTask;
id taskObserver;
NSFileHandle * errorFile;
id errorObserver;
NSFileHandle * outputFile;
id outputObserver;
}
#synthesize textResult,buttonAbort,buttonOK;
- (IBAction)doOK:(id)sender {
[[self window] close];
[NSApp stopModal];
}
- (IBAction)doAbort:(id)sender {
[executionTask terminate];
}
- (void) taskCompleted {
NSLog(#"Task completed");
[[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
[[NSNotificationCenter defaultCenter] removeObserver:errorObserver];
[[NSNotificationCenter defaultCenter] removeObserver:outputObserver];
[self outputAvailable];
[self errorAvailable];
executionTask = nil;
[buttonAbort setEnabled:NO];
[buttonOK setEnabled:YES];
}
- (void) appendText:(NSString *) text inColor:(NSColor *) textColor {
NSDictionary * makeUp = [NSDictionary dictionaryWithObject:textColor forKey:NSForegroundColorAttributeName];
NSAttributedString * extraText = [[NSAttributedString alloc] initWithString:text attributes:makeUp];
[textResult setEditable:YES];
[textResult setSelectedRange:NSMakeRange([[textResult textStorage] length], 0)];
[textResult insertText:extraText];
[textResult setEditable:NO];
[textResult display];
}
- (void) outputAvailable {
NSData * someData = [outputFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"output Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor blackColor]];
}
}
- (void) errorAvailable {
NSData * someData = [errorFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"Error Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor redColor]];
}
}
- (void) runCommand:(NSString *) command {
// make sure all views are initialized
[self showWindow:[self window]];
// some convience vars
NSArray * runLoopModes = #[NSDefaultRunLoopMode, NSRunLoopCommonModes];
NSNotificationCenter * defCenter = [NSNotificationCenter defaultCenter];
// create an task
executionTask = [[NSTask alloc] init];
// fill the parameters for the task
[executionTask setLaunchPath:#"/bin/sh"];
[executionTask setArguments:#[#"-c",command]];
// create an observer for Termination
taskObserver = [defCenter addObserverForName:NSTaskDidTerminateNotification
object:executionTask
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self taskCompleted];
}
];
// Create a pipe and a filehandle for reading errors
NSPipe * error = [[NSPipe alloc] init];
[executionTask setStandardError:error];
errorFile = [error fileHandleForReading];
errorObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:errorFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self errorAvailable];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// Create a pipe and a filehandle for reading output
NSPipe * output = [[NSPipe alloc] init];
[executionTask setStandardOutput:output];
outputFile = [output fileHandleForReading];
outputObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:outputFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self outputAvailable];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// start task
[executionTask launch];
// show our window as modal
[NSApp runModalForWindow:[self window]];
}
My question: Is it possible to update the output while the task is running? And, if yes, how could I achieve that?
A modal window runs the run loop in NSModalPanelRunLoopMode, so you need to add that to your runLoopModes.
You should not be getting the spinning beach ball. The cause is that you're calling -readDataToEndOfFile in your -outputAvailable and -errorAvailable methods. Given that you're using -waitForDataInBackgroundAndNotifyForModes:, you would use the -availableData method to get what data is available without blocking.
Alternatively, you could use -readInBackgroundAndNotifyForModes:, monitor the NSFileHandleReadCompletionNotification notification, and, in your handler, obtain the data from the notification object using [[note userInfo] objectForKey:NSFileHandleNotificationDataItem]. In other words, let NSFileHandle do the work of reading the data for you.
Either way, though, once you get the end-of-file indicator (an empty NSData), you should not re-issue the ...InBackgroundAndNotifyForModes: call. If you do, you'll busy-spin as it keeps feeding you the same end-of-file indicator over and over.
It shouldn't be necessary to manually -display your text view. Once you fix the blocking calls that were causing the spinning color wheel cursor, that will also allow the normal window updating to happen automatically.
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";
}
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.