I have written a program to execute the commands in cocoapp using apple script. There are 2 issues am facing
1) the applescript is exceuting always from the projet directory not from the root
2)cd command is working , but when i do pwd it shows the previous directory name not the new one.
+(BOOL)callAppleScriptForScriptFile:(NSString *)command{
BOOL isError = YES;
NSAppleEventDescriptor* returnDescriptor = NULL;
NSDictionary* errorDict = nil;
NSString *appleScriptCommand = [NSString stringWithFormat:#"do shell script \" %# &> /Users/username/Desktop/.output.txt\" user name \"username\" password \"password\" with administrator privileges",command];
//NSLog(#"Script command %#",appleScriptCommand);
NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:appleScriptCommand];
returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
if (errorDict != NULL){
NSLog(#"%#",errorDict);
isError = NO;
}
DescType descriptorType = [returnDescriptor descriptorType];
NSLog(#"descriptorType == %#", NSFileTypeForHFSTypeCode(descriptorType));
NSData *data = [returnDescriptor data];
double currentPosition = 0;
[data getBytes:¤tPosition length:[data length]];
NSLog(#"currentPosition == %f", currentPosition);
[self readFromFileAndSend];
return isError;
}
Expecting output like this
cd Desktop
o/p: sucess(no need to print)
pwd
o/p: Desktop
Each do shell script statement uses a new shell process, so you can't do something like change the working directory in one statement and then expect it to be the same in another. For example, if you use the separate statements
do shell script "cd ~/Desktop"
do shell script "pwd"
the second statement will show the root directory, because it is starting over - it doesn’t have anything to do with the first statement. You need to include all the commands in the same statement
do shell script "cd ~/Desktop; pwd"
From a Cocoa application, you can also use NSTask, which would avoid all the Apple Event stuff.
Related
I'm invoking various command line tools via NSTask. The tools may run for several seconds, and output text constantly to stdout. Eventually, the tool will terminate on its own. My app reads its output asynchronously with readInBackgroundAndNotify.
If I stop processing the async output as soon as the tool has exited, I will often lose some of its output that hasn't been delivered by then.
Which means I have to wait a little longer, allowing the RunLoop to process pending read notifications. How do I tell when I've read everything the tool has written to the pipe?
This problem can be verified in the code below by removing the line with the runMode: call - then the program will print that zero lines were processed. So it appears that at the time the process has exited, there's already a notification in the queue that is waiting to be delivered, and that delivery happens thru the runMode: call.
Now, it might appear that simply calling runMode: once after the tool's exit may be enough, but my testing shows that it isn't - sometimes (with larger amounts of output data), this will still only process parts of the remaining data.
Note: A work-around such as making the invoked tool outout some end-of-text marker is not a solution I seek. I believe there must be some proper way to do this, whereby the end of the pipe stream is signalled somehow, and that's what I'm looking for in an answer.
Sample Code
The code below can be pasted into a new Xcode project's AppDelegate.m file.
When run, it invokes a tool that generates some longer output and then waits for the termination of the tool with waitUntilExit. If it would then immediately remove the outputFileHandleReadCompletionObserver, most of the tool's output would be missed. By adding the runMode: invocation for the duration of a second, all output from the tool is received - Of course, this timed loop is less than optimal.
And I would like to keep the runModal function synchronous, i.e. it shall not return before it has received all output from the tool. It does run in its own tread in my actual program, if that matters (I saw a comment from Peter Hosey warning that waitUntilExit would block the UI, but that would not be an issue in my case).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self runTool];
}
- (void)runTool
{
// Retrieve 200 lines of text by invoking `head -n 200 /usr/share/dict/words`
NSTask *theTask = [[NSTask alloc] init];
theTask.qualityOfService = NSQualityOfServiceUserInitiated;
theTask.launchPath = #"/usr/bin/head";
theTask.arguments = #[#"-n", #"200", #"/usr/share/dict/words"];
__block int lineCount = 0;
NSPipe *outputPipe = [NSPipe pipe];
theTask.standardOutput = outputPipe;
NSFileHandle *outputFileHandle = outputPipe.fileHandleForReading;
NSString __block *prevPartialLine = #"";
id <NSObject> outputFileHandleReadCompletionObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleReadCompletionNotification object:outputFileHandle queue:nil usingBlock:^(NSNotification * _Nonnull note)
{
// Read the output from the cmdline tool
NSData *data = [note.userInfo objectForKey:NSFileHandleNotificationDataItem];
if (data.length > 0) {
// go over each line
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *lines = [[prevPartialLine stringByAppendingString:output] componentsSeparatedByString:#"\n"];
prevPartialLine = [lines lastObject];
NSInteger lastIdx = lines.count - 1;
[lines enumerateObjectsUsingBlock:^(NSString *line, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx == lastIdx) return; // skip the last (= incomplete) line as it's not terminated by a LF
// now we can process `line`
lineCount += 1;
}];
}
[note.object readInBackgroundAndNotify];
}];
NSParameterAssert(outputFileHandle);
[outputFileHandle readInBackgroundAndNotify];
// Start the task
[theTask launch];
// Wait until it is finished
[theTask waitUntilExit];
// Wait one more second so that we can process any remaining output from the tool
NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:1];
while ([NSDate.date compare:endDate] == NSOrderedAscending) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
[[NSNotificationCenter defaultCenter] removeObserver:outputFileHandleReadCompletionObserver];
NSLog(#"Lines processed: %d", lineCount);
}
It's quite simple. In the observer block when data.length is 0 remove the observer and call terminate.
The code will continue after the waitUntilExit line.
- (void)runTool
{
// Retrieve 20000 lines of text by invoking `head -n 20000 /usr/share/dict/words`
const int expected = 20000;
NSTask *theTask = [[NSTask alloc] init];
theTask.qualityOfService = NSQualityOfServiceUserInitiated;
theTask.launchPath = #"/usr/bin/head";
theTask.arguments = #[#"-n", [#(expected) stringValue], #"/usr/share/dict/words"];
__block int lineCount = 0;
__block bool finished = false;
NSPipe *outputPipe = [NSPipe pipe];
theTask.standardOutput = outputPipe;
NSFileHandle *outputFileHandle = outputPipe.fileHandleForReading;
NSString __block *prevPartialLine = #"";
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleReadCompletionNotification object:outputFileHandle queue:nil usingBlock:^(NSNotification * _Nonnull note)
{
// Read the output from the cmdline tool
NSData *data = [note.userInfo objectForKey:NSFileHandleNotificationDataItem];
if (data.length > 0) {
// go over each line
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *lines = [[prevPartialLine stringByAppendingString:output] componentsSeparatedByString:#"\n"];
prevPartialLine = [lines lastObject];
NSInteger lastIdx = lines.count - 1;
[lines enumerateObjectsUsingBlock:^(NSString *line, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx == lastIdx) return; // skip the last (= incomplete) line as it's not terminated by a LF
// now we can process `line`
lineCount += 1;
}];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object:nil];
[theTask terminate];
finished = true;
}
[note.object readInBackgroundAndNotify];
}];
NSParameterAssert(outputFileHandle);
[outputFileHandle readInBackgroundAndNotify];
// Start the task
[theTask launch];
// Wait until it is finished
[theTask waitUntilExit];
// Wait until all data from the pipe has been received
while (!finished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.0001]];
}
NSLog(#"Lines processed: %d (should be: %d)", lineCount, expected);
}
The problem with waitUntilExit is that it doesn't always behave the way one might think. The following is mentioned in the documenation:
waitUntilExit does not guarantee that the terminationHandler
block has been fully executed before waitUntilExit returns.
It appears this is precisely the problem you are having; it's a race condition. The waitUntilExit is not waiting long enough and the lineCount variable is reached before the NSTask completes. The solution would likely be to use a semaphore or dispatch_group, although it's unclear if you want to go that route — this is not an easy problem to resolve it seems.
*I experienced a similar issue from months back that still isn't resolved unfortunately.
I have a command (xcodebuild) that if I try in terminal it works very well. When I try to put it on my code:
let xcodeProjectPath = "/Users/xxx/Desktop/Code/xxx.xcworkspace"
let xcodeArchivePath = "/Users/xxx/Desktop/xxx.xcarchive"
let schemeName = "XXX"
let pid = NSProcessInfo.processInfo().processIdentifier
let pipe: NSPipe = NSPipe()
let file: NSFileHandle = pipe.fileHandleForReading
let archiveCommand = "xcodebuild -scheme \(schemeName) -workspace \(xcodeProjectPath) -configuration Release -archivePath \(xcodeArchivePath) archive"
let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = NSArray(objects: "-l", "/Users/xxx/Desktop/test.sh") as [AnyObject]
task.standardInput = NSPipe()
task.standardOutput = pipe
task.launch()
let data = file.readDataToEndOfFile()
file.closeFile()
let grepOutput = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Returned: \(grepOutput!)")
It doesn't work and return me:
** ARCHIVE FAILED **
The following build commands failed:
DataModelVersionCompile /Users/xxx/Library/Developer/Xcode/DerivedData/XXX-fbrisxgdcevajabbkhkejvwjrxyt/Build/Intermediates/ArchiveIntermediates/XXX/InstallationBuildProductsLocation/Applications/XXX.app/Database.momd Application/Resources/Database/Database.xcdatamodeld
My script.sh is:
#!/bin/sh
xcodebuild -scheme XXX -workspace /Users/xxx/Desktop/Codice/xxx.xcworkspace -configuration Release -archivePath /Users/xxx/Desktop/provaBuild.xcarchive archive
Any idea? :(
After many attempts I discover that if I try my code running app with Xcode it doesn't works. If i build the mac app... everything works!
What you are trying to accomplish?
You archiveCommand variable is never used and the second NSTask is initialized with an external sh file.
Anyway I think you are trying to read the pipe before the operation ends; I think waitUntilExit method just before launch command should help (or at least use the termination handler).
This is a test code with the relevant part of your question; it should works fine (I've written it in ObjC but the translation is very straightforward).
NSArray *args = #[ #"-scheme",schemePath,#"-workspace",prjPath,#"-configuration",#"Release",#"-archivePath",archPath,#"archive"];
[task setArguments:args];
NSPipe *outputPipe = [NSPipe pipe];
[task setStandardOutput:outputPipe];
[task setTerminationHandler:^(NSTask *task) {
NSFileHandle * read = [outputPipe fileHandleForReading];
NSData * dataRead = [read readDataToEndOfFile];
NSString * stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding];
NSLog(#"output: %#", stringRead);
}];
[task launch];
My code looks like this:
NSMutableString* cmd = [NSMutableString new];
[cmd appendString:#"script createScriptFile\n"];
[cmd appendString:#"beep\n"];
[cmd appendString:#"end script\n"];
[cmd appendString:#"store script createScriptFile in \"/tmp/narg.scpt\"\n"];
NSAppleScript* script = [[NSAppleScript alloc] initWithSource:cmd];
NSDictionary* err = nil;
NSAppleEventDescriptor *result = [script executeAndReturnError:&err];
which runs fine without errors, but the resulting script file (narg.scpt) looks like this:
script createScriptFile
beep
end script
store script createScriptFile in "/tmp/narg.scpt"
and what I want is for narg.sctp to look like this:
beep
What am I doing wrong?
Thanks,
Chris
I would like to call a IBAction within a cocoa app from applescript:
I want to call:
- (IBAction)reverse:(id)pId;
{
direction = -1;
}
with a line in an external applescript file like:
tell application "theapp"
reverse
end tell
Any Ideas?
Thanks in Advance
Use NSAppleScript.
NSAppleScript *as = [[NSAppleScript alloc] initWithSource:#"tell application \"theapp\"\nreverse\nend tell"];
NSDictionary *err = nil;
NSAppleEventDescriptor *desc = [as executeAndReturnError:&err];
NSLog(#"Error: %#\nData: %#", err, desc.data);
[as release];
There is also a good answer about Scripting Bridge here
I have a zipped file, which i want to extract the contents of it. What is the exact procedure that i should do to achieve it. Is there any framework to unzip the files in cocoa framework or objective C.
If you are on iOS or don't want to use NSTask or whatever, I recommend my library SSZipArchive.
Usage:
NSString *path = #"path_to_your_zip_file";
NSString *destination = #"path_to_the_folder_where_you_want_it_unzipped";
[SSZipArchive unzipFileAtPath:path toDestination:destination];
Pretty simple.
On the Mac you can use the built in unzip command line tool using an NSTask:
- (void) unzip {
NSFileManager* fm = [NSFileManager defaultManager];
NSString* zipPath = #"myFile.zip";
NSString* targetFolder = #"/tmp/unzipped"; //this it the parent folder
//where your zip's content
//goes to (must exist)
//create a new empty folder (unzipping will fail if any
//of the payload files already exist at the target location)
[fm createDirectoryAtPath:targetFolder withIntermediateDirectories:NO
attributes:nil error:NULL];
//now create a unzip-task
NSArray *arguments = [NSArray arrayWithObject:zipPath];
NSTask *unzipTask = [[NSTask alloc] init];
[unzipTask setLaunchPath:#"/usr/bin/unzip"];
[unzipTask setCurrentDirectoryPath:targetFolder];
[unzipTask setArguments:arguments];
[unzipTask launch];
[unzipTask waitUntilExit]; //remove this to start the task concurrently
}
That is a quick and dirty solution. In real life you will probably want to do more error checking and have a look at the unzip manpage for fancy arguments.
Here's a Swift 4 version similar to Sven Driemecker's answer.
func unzipFile(at sourcePath: String, to destinationPath: String) -> Bool {
let process = Process.launchedProcess(launchPath: "/usr/bin/unzip", arguments: ["-o", sourcePath, "-d", destinationPath])
process.waitUntilExit()
return process.terminationStatus <= 1
}
This implementation returns a boolean that determines if the process was successful or not. Is considering the process successful even if warnings were encountered.
The return condition can be changed to return process.terminationStatus == 0 to not even accept warnings.
See unzip docs for more details on diagnostics.
You can also capture the output of the process using a Pipe instance.
func unzipFile(at sourcePath: String, to destinationPath: String) -> Bool {
let process = Process()
let pipe = Pipe()
process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
process.arguments = ["-o", sourcePath, "-d", destinationPath]
process.standardOutput = pipe
do {
try process.run()
} catch {
return false
}
let resultData = pipe.fileHandleForReading.readDataToEndOfFile()
let result = String (data: resultData, encoding: .utf8) ?? ""
print(result)
return process.terminationStatus <= 1
}
If you just want to unzip the file, I would recommend using NSTask to call unzip(1). It's probably smart to copy the file to a directory you control -- probably in /tmp -- before unzipping.
Here's a more concise version based on codingFriend1's approach
+ (void)unzip {
NSString *targetFolder = #"/tmp/unzipped";
NSString* zipPath = #"/path/to/my.zip";
NSTask *task = [NSTask launchedTaskWithLaunchPath:#"/usr/bin/unzip" arguments:#[#"-o", zipPath, #"-d", targetFolder]];
[task waitUntilExit];
}
-d specifies the destination directory, which will be created by unzip if not existent
-o tells unzip to overwrite existing files (but not to deleted outdated files, so be aware)
There's no error checking and stuff, but it's an easy and quick solution.
Try Zip.framework.
-openFile: (NSWorkspace) is the easiest way I know.