Shell script with NSTask doesn't work - xcode

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];

Related

OS X return to Xcode swift from shell

I have code which invokes shell. Shell does some testing of the arguments and if they pass, it runs.
Right now, I post any errors to a log file but would like to return them to my swift program ...
let bundle = NSBundle.mainBundle()
let cmd = bundle.pathForResource("model", ofType: "sh")
let task = NSTask()
task.launchPath = cmd
task.arguments = [ "\(arg1.stringValue)", "\(arg2.stringValue)" ]
task.launch()
This works but how do I get the output of the shell short of reading the log file created in the shell.
Ran into this. Hope it helps.
http://practicalswift.com/2014/06/25/how-to-execute-shell-commands-from-swift/
#!/usr/bin/env xcrun swift -i
import Foundation
let task = NSTask()
task.launchPath = "/bin/echo"
task.arguments = ["first-argument", "second-argument"]
let pipe = NSPipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: NSUTF8StringEncoding)
print(output)
assert(output == "first-argument second-argument\n")
A project posted at github may be useful also:
https://github.com/kareman/SwiftShell

NSProcessInfo returns different PATH than "echo $PATH"

I am trying to programatically figure out whether there is a specific binary in the system PATH. To get the environment I used both
NSString* path = [[[NSProcessInfo processInfo] environment] objectForKey:#"PATH"];
and
NSString* path2 = [NSString stringWithUTF8String: getenv("PATH")];
both yielding the same result, in both cases different then echo $PATH in console. Both path and path2 does not contain paths set via /etc/paths.d, so the question is how to get the the environment PATH as returned from console programatically?
NSProcessInfo will just access information about current process. For example below i am executing the same echo $PATH command in cocoa and am getting the same output which NSProcessInfo is displaying. So in the terminal when you execute the same command. You will get different ouput. Because it is showing the path of current process in terminal. If you want to see the same output of both you can execute this command in terminal launchctl getenv PATH which will be equivalent to [[[NSProcessInfo processInfo] environment] objectForKey:#"PATH"];
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/bash"];
[task setArguments:[NSArray arrayWithObjects: #"-c", #"echo $PATH",nil]];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *response = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"%#",response);

Forcing Python to run in 64 mode from a NSTask subprocess

What is a method that forces python to run in 64 mode from a NSTask?
Update: As per Ned's suggestion I tried referencing Python2.7 directly with objective c and that worked. Changed #"/usr/bin/python" to #"/usr/bin/python2.7". The new code is at the bottom of the question.
I have a 64 bit system. When run from terminal python runs in 64 bit.
When I run a plain shell from a NSTask running /usr/bin/uname -m it returns x86_64.
I've tried using arch but the shell running python is still in 32 bit mode.
Example method
-(void) runPython64BitScriptViaArchWithPath:(NSString*)path {
NSTask* task = [[NSTask alloc] init];
task.launchPath = #"/usr/bin/arch" ;
task.arguments = [NSArray arrayWithObjects: #"-x86_64", #"/usr/bin/python", path, nil];
[task setStandardInput:[NSPipe pipe]] ;
NSPipe *stdOutPipe = nil;
stdOutPipe = [NSPipe pipe];
[task setStandardOutput:stdOutPipe];
NSPipe* stdErrPipe = nil;
stdErrPipe = [NSPipe pipe];
[task setStandardError: stdErrPipe];
NSLog(#"%#", [task arguments]) ;
[task launch] ;
NSData* data = [[stdOutPipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
NSInteger exitCode = task.terminationStatus;
if (exitCode != 0)
{
NSLog(#"Error!");
NSData *error = [[stdErrPipe fileHandleForReading] readDataToEndOfFile] ;
NSLog(#"Exit code : %ld", (long)exitCode) ;
NSString *result = [[NSString alloc] initWithBytes: error.bytes length:error.length encoding: NSUTF8StringEncoding] ;
NSLog(#"%#",result);
[result release];
} else {
NSString *result = [[NSString alloc] initWithBytes: data.bytes length:data.length encoding: NSUTF8StringEncoding] ;
NSLog(#"%#",result) ;
[result release];
}
[task release] ;
}
an example python script that works if run from terminal
#!/usr/bin/env python
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'social_shields.settings'
try:
import hosts.models
shield = hosts.models.Shield.objects.all()[0]
shield.active = True
shield.save()
except Exception as exception:
import struct
print 'Bits : %s' % ( 8 * struct.calcsize("P"))
print exception
example log
2013-07-04 16:10:31.600 socialshield[88688:303] onShieldDown
2013-07-04 16:10:31.607 socialshield[88688:303] x86_64
2013-07-04 16:10:31.607 socialshield[88688:303] (
"-x86_64",
"/usr/bin/python",
"/source/social_shields/social_shields/shield_down.py"
)
2013-07-04 16:10:31.933 socialshield[88688:303] Bits : 32
Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-packages/_mysql.so, 2): no suitable image found. Did find:
/Library/Python/2.7/site-packages/_mysql.so: mach-o, but wrong architecture
new code that works after applying Ned's suggestion :-)
-(void) runPython64BitScriptViaArchWithPath:(NSString*)path {
NSTask* task = [[NSTask alloc] init];
task.launchPath = #"/usr/bin/arch" ;
task.arguments = [NSArray arrayWithObjects: #"-x86_64", #"/usr/bin/python2.7", path, nil];
[task setStandardInput:[NSPipe pipe]] ;
NSPipe *stdOutPipe = nil;
stdOutPipe = [NSPipe pipe];
[task setStandardOutput:stdOutPipe];
NSPipe* stdErrPipe = nil;
stdErrPipe = [NSPipe pipe];
[task setStandardError: stdErrPipe];
NSLog(#"%#", [task arguments]) ;
[task launch] ;
NSData* data = [[stdOutPipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
NSInteger exitCode = task.terminationStatus;
if (exitCode != 0)
{
NSLog(#"Error!");
NSData *error = [[stdErrPipe fileHandleForReading] readDataToEndOfFile] ;
NSLog(#"Exit code : %ld", (long)exitCode) ;
NSString *result = [[NSString alloc] initWithBytes: error.bytes length:error.length encoding: NSUTF8StringEncoding] ;
NSLog(#"%#",result);
[result release];
} else {
NSString *result = [[NSString alloc] initWithBytes: data.bytes length:data.length encoding: NSUTF8StringEncoding] ;
NSLog(#"%#",result) ;
[result release];
}
[task release] ;
}
I assume you have verified that /Library/Python/2.7/site-packages/_mysql.so is 64-bit and that you are really using the same Python in both cases. On OS X 10.6 and later systems, /usr/bin/python is actually a wrapper executable that determines which version of Python and which architecture (32-bit or 64-bit) to run; see man 1 python for details. Try executing /usr/bin/python2.7 directly. That should default to 64-bit. You can also check whether Python is running in 32- or 64-bit mode by using this documented test:
import sys; print(sys.maxsize > 2**32)

NSTask and arguments when running command line tools

How would I pass arguments (host in this case) to NSTask in this code? It does not accept the host NSString. If I pass the host value with the ping, for e.g..
[NSArray arrayWithObjects:#"-c",#"ping -c 5 www.google.com",nil]
then it works. But it won't take the host argument separately. Thanks for the help in advance.
task = [[NSTask alloc] init];
[pipe release];
pipe = [[NSPipe alloc] init];
[task setStandardInput: [NSPipe pipe]];
[task setLaunchPath:#"/bin/bash"];
NSArray *args = [NSArray arrayWithObjects:#"-c",#"ping -c 5",host,nil];
[task setArguments:args];
[task setStandardOutput:pipe];
NSFileHandle *fh = [pipe fileHandleForReading];
Use stringWithFormat method of NSString class
task = [[NSTask alloc] init];
[pipe release];
pipe = [[NSPipe alloc] init];
[task setStandardInput: [NSPipe pipe]];
[task setLaunchPath:#"path"];
NSArray *args = [NSArray arrayWithObjects:#"-c",[NSString stringWithFormat: #"%# %# %# %#",#"ping",#"-c",#"5",host],nil];
[task setArguments:args];
[task setStandardOutput:pipe];
NSFileHandle *fh = [pipe fileHandleForReading];
Your arguments are not correct. First of all, you should set the launchpath to /bin/ping, or wherever the task is located, then the arguments should be an array of the arguments you would normally enter on the command line, but then seperated by spaces there..
Please take a look at this tutorial Wrapping UNIX commands for more information on how to do this properly.
NSMutableArray *args = [NSMutableArray array];
NSArray *args = [NSArray arrayWithObjects:#"-c", #"\"ping -c 5", host, #"\"",nil]
[task setArguments:args];
Bash -c needs to take your command in quotes.

unzipping a file in cocoa

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.

Resources