How to insert some data into a string of AppleScript - objective-c

I have a function that sends a string "theData". I want to insert that string here in this code. Would someone please tell me the correct syntax for this? Things get a bit hairy with the \"s and the "s. Thanks!
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/osascript"];
[task setArguments:[NSArray arrayWithObjects:#"-e", #"tell application \"System Events\"\n",
#"-e", #" keystroke \"" + theData + "\"",
#"-e", #"end tell", nil]];
[task launch];

icktoofay already gave the more correct answer, but let me just show how to insert a string in a string:
NSString* toBeInserted = #"for";
NSString* result = [NSString stringWithFormat:#"in%#mation",toBeInserted];
NSLog(#"%#",result);
This gives information. For more details, read Apple's doc.
I mean, Apple's doc is quite good, actually. Read it before asking a question here at SO.
By the way, you don't have to launch osascript to execute AppleScript. You can use NSAppleScript as in
NSAppleScript* script=[[NSAppleScript alloc] initWithSource:#"tell app \"Finder\" to activate "];
NSDictionary*error;
[script executeAndReturnError:&error];
[script release];
Well, NSAppleScript is an oddity which requires NSDictionary, not an NSError, to report an error ...
Or, you can use Scripting Bridge to map AppleScript objects to Objective-C objects.

I see you have another way of doing it, but you can use format strings to accomplish this
[NSString stringWIthFormat: #"part one %# part 2", theData];
Assuming the data is an nsstring containing "hello", this will give you:
#"part one hello part 2"

Related

NSAppleEventDescriptor huge memory leak, obj-c

NSDictionary *error = nil;
//AppleScript to get all running windows
NSAppleScript *appleScriptFindWindows = [[NSAppleScript alloc] initWithSource:
#"tell application \"System Events\" to get the title of every window of process \"TestWindow\" whose name contains \"Black\" end tell"];
while (true) {
#autoreleasepool {
//Execute and get the result of the OSAScript
NSAppleEventDescriptor *result = [appleScriptFindWindows executeAndReturnError:&error];
//Convert the result to a string
NSString *windowNames = [NSString stringWithFormat:#"%#", result];
error = nil;
sleep(0.25);
}
}
I know I am not currently doing anything with result but I will do once I have the issue fixed.
I will be monitoring various windows/files using applescript on a continuous loop, however I have noticed that when I run this code my memory usage skyrockets at 12mb/s and energy impact is high. I cannot release or de-alloc AppleEventDescriptor because of arc.
Is there a way to release the event descriptor or perhaps I am missing something in the applescript itself to correctly exit after execution?
I am a bit lost on this one and being new to obj-c I am wondering if there is a better way to execute applescript within obj-c if that is the issue.

I need to put an NSString variable in another string inside the initWithSource function, but I don't know how

I need to put an NSString variable between another string code to get the 'pathToSerialDevice' variable between the other code. Like this:
NSAppleScript *appleScript = [[NSAppleScript alloc]
initWithSource:#"Tell application \"Terminal\" \n\
do script with command \"screen %#", pathToSerialDevice, "\" in front window\n\
end tell"];
And my pathToSerialDevice string is taken from a text field
pathToSerialDevice = [NSString stringWithFormat:_pathTextField.stringValue];
When I display it in the Log, it works, I do it so:
NSLog(#"Your path is %#", pathToSerialDevice);
How to do the same, but in the NSAppleScript? It doesn't work now and I have no idea how to do it. Please help me.
P.S.
I have Xcode 6.1 on OS X 10.10 and it's an OS X app.
initWithSource: takes a regular string, not a format string. Read these docs on the difference.
The easiest thing to do is to just make another string:
NSString *pathToSerialDevice = [NSString stringWithFormat:_pathTextField.stringValue];
NSString *source = [NSString stringWithFormat:#"Tell application \"Terminal\" \n\
do script with command \"screen %# \" in front window\n\
end tell", pathToSerialDevice];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:source];

Run user defined command with NSTask

I would like to execute a terminal command specified by the user. For example, the user might write killall "TextEdit" or say "Hello world!" in a text field, and I want to execute that command.
NSTask is the way to go, except I have two problems with it:
First: the arguments. Right now I'm doing this:
NSArray* args = [commandString componentsSeparatedByString: #" "];
[task setArguments: [args subarrayWithRange: NSMakeRange(1, [args count] - 1)]]; // First one is the command name
Is this the way to do it? I don't think I've had problems with this yet, but I doesn't look like it's safe. Imagine this: the user writes killall 'Address Book' but the command receives as arguments 'Address and Book'?? That doesn't work. So, what should I do instead? How can I safely parse the arguments?
Second: the launch path. It's much more user-friendly to only have to write the name of the command, instead of the complete path to it. So I want to support that, which means finding out programmatically the full path for a command having only it's name. For that I wrote a category on NSTask like this:
+ (NSString*)completePathForExec: (NSString*)exec
{
NSTask* task = [[NSTask alloc] init];
NSPipe* pipe = [[NSPipe alloc] init];
NSArray* args = [NSArray arrayWithObject: exec];
[task setLaunchPath: #"/usr/bin/which"];
[task setArguments: args];
[task setStandardOutput: pipe];
[task setStandardError: pipe];
[task launch];
[task waitUntilExit];
NSFileHandle* file = [pipe fileHandleForReading];
NSString* result = [[NSString alloc] initWithData: [file readDataToEndOfFile] encoding: NSASCIIStringEncoding];
if ([result length]) {
if ([result hasSuffix: #"\n"]) { result = [result substringWithRange: NSMakeRange(0, [result length] - 1)]; }
return result;
}
else { return exec; }
}
This seems to works well. However, how can I be sure that this path: /usr/bin/which will always work? I mean: will it work on 10.6, 10.7, 10.8, etc? I think I had a problem once where the path to a shell command changed with the system version, and you can never be too careful.
If the path is guaranteed to stay the same, then this isn't a problem. If it changes, then how can I know the 'path to the path-finder'?
It'll be far easier for you to not re-invent the command line parsing wheel. But, of course, going down the route of executing arbitrary user entered code is a security nightmare (tempered by the fact that the user has access to the system and, thus, could probably just run Terminal directly).
Specifically, have NSTask wrap an invocation of one of the shells with the command line option to have it execute an arbitrary string.
sh -c "ls -alF"
This would allow you to pass the path to sh as your launch path, which is in a fixed location on every system. The #"-c" argument tells sh to parse the next argument as a script and, of course, the next argument is whatever the user entered.
Note, this will also give the user the ability to pipe stuff, too.

NSTask Question

I'm trying to get my NSTask to unzip a file for me. This works fine if the path has no spaces, but when it does, it can't find any of the files. I can't hardcode the " signs because I'm storing the zip file in a temporary folder, which is assigned by the system.
Does anyone know how to achieve this?
Here's my code:
NSTask*task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/unzip"];
NSArray*arguments = [NSArray arrayWithObjects:zipPath,#"-d",path,nil];
[task setArguments:arguments];
[task launch];
[task release];
Why can't you embed the quote marks?
NSString *quotedPath = [NSString stringWithFormat:#"\"%#\"", path];
NSArray *arguments = [NSArray arrayWithObjects:zipPath, #"-d", quotedPath, nil];
Having a space in the argument does not look like your problem - note that the console is showing the pathname with a space. An argument with a space is passed as a single argument, I've just confirmed it will happily unzip #"a space.zip". Have you checked the file does exist where you think it does and you have access to it?
Could you parse the path components using NSString's - (NSArray *)pathComponents method, add the quotes where needed, then rebuild the string using (NSString *)pathWithComponents:(NSArray *)components
Does that work?

Registering for a NSFileHandleReadToEndOfFileCompletionNotification

I'm trying to figure out how to make this piece of code from a previous question work, but I'm stuck on the part on how to 'register' a NSFileHandleReadToEndOfFileCompletionNotification.
This is my code:
NSTask *topTask = [NSTask new];
[topTask setLaunchPath:#"/usr/bin/top"];
[topTask setArguments:[NSArray arrayWithObjects:#"-s", #"1", #"-l", #"3600", #"-stats", #"pid,cpu,time,command", nil]];
NSPipe *outputPipe = [NSPipe pipe];
[topTask setStandardOutput:outputPipe];
[topTask launch];
... which runs fine until I add this:
[[outputPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
... which causes the program to freeze. And when I add this:
NSString *outputString = [[[NSString alloc] initWithData:[[notification userInfo] objectForKey:NSFileHandleNotificationDataItem] encoding:NSUTF8StringEncoding] autorelease];
... the code doesn't compile and I get the !warning
'notification' undeclared.
Any assistance on this matter earns copious amounts of gratitude on my behalf.
Running top with those parameters in the command line will cause it to continually print out stats and it will never write EOF. That's why -readToEndOfFileInBackgroundAndNotify runs forever.
It sounds like you may just want to read the first iteration and then kill the task.
As for the error, it sounds like you simply don't have a variable named notification in the method containing that line.