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
Difference Between Firinstanceid.Instanceid().Token() and Messaging.Messaging().Fcmtoken
How to Test If an Instance Is a Specific Class or Type in Swift
Swift iOS14 Datepicker Text Alignment
Preferredstatusbarupdateanimation Being Ignored
Macos Security Scoped Url Bookmark for Folder
Get Path of a File in a Data Set Located in Assets.Xcassets
Include Dictionary or Array Value in Swift String with Backslash Notation
Swift Combine How Set<Anycancellable> Works
Destructuring Tuple of Tuple in Closure
How to Put View on Top of All Other Views in Swiftui
iOS Swift, Aws Cognito User Pool, Unable to Refresh Access Token
Produce Sounds of Different Frequencies in Swift
Swift 2.0 Replicate Objc_Association_Retain
Implement Protocol with Different Associated Type
How to Get Access Token from Instagram API
All of My UIalertcontroller Messages Became Single Line