Running Terminal Commands from Cocoa App in Swift Failing: "Command Not Found" But Works Using Command Line Tool in Swift

Running terminal commands from Cocoa App in Swift failing: command not found but works using Command Line Tool in swift

Add "--login" as the first task argument:

task.arguments = ["--login", "-c", "dayone2 new 'Hello'"]

and that should fix your error.

Explanation:

When you run Terminal the shell starts up as a login shell, from man sh:

When bash is invoked as an interactive login shell, or as a
non-inter-active shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.

Among other things the commands in these files typically set the $PATH environment variable, which defines the search path the shell uses to locate a command.

When you run your command line tool in the Terminal it inherits this environment variable and in turn passes it on to the shell it invokes to run your dayone2 command.

When you run a GUI app there is no underlying shell and the $PATH variable is set to the system default. Your error "command not found" indicates that your dayone2 command is not on the default path.

HTH

Running terminal commands in in cocoa app

Simple answer is found by disabling App Sandbox in your Cocoa Application (found under your Project app target > Capabilities tab > App Sandbox switch). You'll find that you're being blocked by a sandbox exception. Disabling sandboxing should fix your issue.

You can also see this in Console.app if you filter for your app name or the sandboxd process. You'll likely have an entry like this when sandboxing is enabled:

error 00:21:57.502273 +0000 sandboxd Sandbox: sh(17363) deny(1) file-read-data /dev/ttys003

Execute a terminal command from a Cocoa app

You can use NSTask. Here's an example that would run '/usr/bin/grep foo bar.txt'.

int pid = [[NSProcessInfo processInfo] processIdentifier];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;

NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/grep";
task.arguments = @[@"foo", @"bar.txt"];
task.standardOutput = pipe;

[task launch];

NSData *data = [file readDataToEndOfFile];
[file closeFile];

NSString *grepOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (@"grep returned:\n%@", grepOutput);

NSPipe and NSFileHandle are used to redirect the standard output of the task.

For more detailed information on interacting with the operating system from within your Objective-C application, you can see this document on Apple's Development Center: Interacting with the Operating System.

Edit: Included fix for NSLog problem

If you are using NSTask to run a command-line utility via bash, then you need to include this magic line to keep NSLog working:

//The magic line that keeps your log where it belongs
task.standardOutput = pipe;

An explanation is here: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

How do I run a terminal command in a Swift script? (e.g. xcodebuild)

If you don't use command outputs in Swift code, following would be sufficient:

#!/usr/bin/env swift

import Foundation

@discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}

shell("ls")
shell("xcodebuild", "-workspace", "myApp.xcworkspace")

Updated: for Swift3/Xcode8

Terminal test command is not working as expected in Swift

You have two problems here.

The reason for the error is because you have a test directory in the current directory and (because of the second error) the shell is trying to execute it as a script and failing.

The second error is that you don't pass commands to the shell to run directly as independent arguments.

That is you don't do this:

/bin/sh test -f test.dmg && rm test.dmg

What you do is you use the -c flag to the shell and pass the entire command as a single string to it:

/bin/sh -c 'test -f test.dmg && rm test.dmg'

Which would make your code something like this:

let arguments = ["-c" "test -f test.dmg && rm test.dmg"]
shell("/bin/sh", arguments)


Related Topics



Leave a reply



Submit