How to run terminal command in swift from any directory?
There is a deprecated property currentDirectoryPath
on Process
.
On the assumption you won't want to use a deprecated property, after reading its documentation head over to the FileManager
and look at is provisions for managing the current directory and their implications.
Or just use cd
as you've considered – you are launching a shell (zsh
) with a shell command line as an argument. A command line can contain multiple commands separated by semicolons so you can prepend a cd
to your command
value.
The latter approach avoids changing your current process' current directory.
HTH
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
How to execute terminal command in swift?
Full examples:
- go to some directory, let say
Desktop
- Create a file with name
swsh
, and add into it (plaintext, not rtf, or doc)
#!/usr/bin/env xcrun swift
import Foundation
func shell(launchPath: String, arguments: [String]) -> String {
let process = Process()
process.launchPath = launchPath
process.arguments = arguments
let pipe = Pipe()
process.standardOutput = pipe
process.launch()
let output_from_command = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8)!
// remove the trailing new-line char
if output_from_command.characters.count > 0 {
let lastIndex = output_from_command.index(before: output_from_command.endIndex)
return output_from_command[output_from_command.startIndex ..< lastIndex]
}
return output_from_command
}
let output = shell(launchPath: "/bin/date", arguments: [ ])
print(output)
Save, and:
- open the Terminal
- type
cd ~/Desktop
- use
chmod 755 swsh
- and run your
swift script
as:./swsh
You will get output like:
Sat Mar 25 14:31:39 CET 2017
Edit your swsh
and change the shell(...
line to:
let output = shell(launchPath: "/usr/bin/env", arguments: [ "date" ])
run it and again will get the date, but now:
- the
swsh
executed the/usr/bin/env
, (with the argumentdate
) - and the
/usr/bin/env
finds the commanddate
- and executed it
Now, create another file in the ~/Desktop
and name it as from_swift
.
Add into it
echo "Today's date is $(date)"
change the file swsh
change the shell
line to:
let output = shell(launchPath: "./from_swift", arguments: [ ])
Note, the ./from_swift
- using relative path to .
(we are in the ~/Desktop
directory). Run the swift program:
./swsh
Output:
2017-03-25 14:42:20.176 swift[48479:638098] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'launch path not accessible'
Of course, the script from_swift
is not executable yet. So execute:
chmod 755 from_swift
# and run
./swsh
Again error:
2017-03-25 14:45:38.523 swift[48520:639486] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Couldn't posix_spawn: error 8'
This is because the from_swift
is a script (not a compiled binary), so the operating system need to know which binary should interpret the script content. Because this is an shell script
edit the from_swift
script as:
#!/bin/sh
echo "Today's date is $(date)"
Note the added "shebang" line: #!/bin/sh
. Run the swift ./swsh
and will get
Today's date is Sat Mar 25 14:50:23 CET 2017
Horray, you executed your 1st bash
script from swift
. ;)
Of course, you can use the /usr/bin/env
in the shebang
, so now change, the content of the from_swift
for example to:
#!/usr/bin/env perl
use strict;
use utf8;
use English;
binmode STDOUT, ":utf8";
printf "The $EXECUTABLE_NAME (ver:$PERL_VERSION) runs me: $0\n";
printf "I ❤️ perl!\n";
Run the ./swsh
and will get:
The /usr/bin/perl (ver:v5.18.2) runs me: ./from_swift
I ❤️ perl!
NOTE, we changed nothing in the ./swsh
file, just the script file ./from_swift
!
All the above done using:
$ uname -a
Darwin nox.local 16.4.0 Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64 x86_64
$ swift --version
Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
Target: x86_64-apple-macosx10.9
So, it is easy to create and execute any script. So, you can enter into your ~/Desktop/from_swift
#!/bin/sh
cd $HOME/Desktop/firebase-mac
npm start
It is possible to do directly from the swsh
, (Jens Meder proposed), but using this you got very easy method executing anything from the given script file.
Just remember: the process.launch()
executes either:
- compiled binaries
- or script files, but the script files
- must have the
shebang
line - and must be executable using
chmod 755 /path/to/script.file
- must have the
Running shell commands in Swift
Looked into this a bit more.
The nodejs
installer uses /usr/local/bin
, which is not included in the PATH
for applications launched from Finder:
/usr/bin:/bin:/usr/sbin:/sbin
The directory is included in the PATH
set for bash
via /etc/profile
& path_helper:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
Options:
Just write
/usr/local/bin/node
instead ofnode
.Tweak the
PATH
used byNSTask
via theenvironment
property.Use setenv to change the
PATH
forsystem
— will also affectNSTask
.- Execute
bash
as a login shell. See the man page for details.
How do I run shell command in swift?
It's possible to access to a path like /Users/myName/myWorkspace
,
but you have first, to disable the app sandbox doing so:
If you want to run ls
in a custom directory, you might try this example:
let res = Helper.shell(launchPath: "/bin/ls", arguments: ["/Users/myUserName/myworkspace"])
print("*** ls ***:\n\(res)")
in my case I have the following output:
*** ls ***:
file1.txt
file2.txt
file3.txt
How can I run macOS terminal commands from Swift?
try something like this:
Note: you need to delete the "App Sandbox" in the "Signing & Capabilities"
struct ContentView: View {
let arg = NSHomeDirectory() + "/Desktop/newfolder"
var body: some View {
Button("mkdir") {
runMkdir()
}.frame(width: 333, height: 333)
}
func runMkdir() {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/mkdir")
task.arguments = [arg]
task.launch()
}
}
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
Correctly execute shell commands in Swift 5
This is how I do it to start ffmpeg in my app. (ffmpegTask
is a Process!
instance, declared as an instance property, and prefs.input_uri
is a String, coming from user input -It is the URI of an RTSP stream). Hope this helps (the "TemporaryFile" thing is from Ole Begemann's excellent utility):
/* ################################################################## */
/**
This starts the ffmpeg task.
- returns: True, if the task launched successfully.
*/
func startFFMpeg() -> Bool {
ffmpegTask = Process()
// First, we make sure that we got a Process. It's a conditional init.
if let ffmpegTask = ffmpegTask {
// Next, set up a tempdir for the stream files.
if let tmp = try? TemporaryFile(creatingTempDirectoryForFilename: "stream.m3u8") {
outputTmpFile = tmp
// Fetch the executable path from the bundle. We have our copy of ffmpeg in there with the app.
if var executablePath = (Bundle.main.executablePath as NSString?)?.deletingLastPathComponent {
executablePath += "/ffmpeg"
ffmpegTask.launchPath = executablePath
ffmpegTask.arguments = [
"-i", prefs.input_uri,
"-sc_threshold", "0",
"-f", "hls",
"-hls_flags", "delete_segments",
"-hls_time", "4",
outputTmpFile?.fileURL.path ?? ""
]
#if DEBUG
if let args = ffmpegTask.arguments, 1 < args.count {
let path = ([executablePath] + args).joined(separator: " ")
print("\n----\n\(String(describing: path))")
}
#endif
// Launch the task
ffmpegTask.launch()
#if DEBUG
print("\n----\n")
#endif
return ffmpegTask.isRunning
}
}
}
return false
}
Sandboxing is likely to be an issue, depending on where you are calling. ffmpeg doesn't like sandboxes anyway, but I still embed the variant that I build for my app in the same directory as the main app executable.
Related Topics
Sandbox Entitlement to Script Itunes via Nsapplescript
How to Disabled Some Default Functionality in Scene View When Allowscameracontrol = True
Swift: How to Continuously Send an Action from a Nstextfield
Swift-Making an Sknode Simply "Move Forward" on an Angle
Saving a Screen Recording with Rpscreenrecorder Start Capture
Hit Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Value (Lldb)
Hide Placeholder Programatically Using Swift 3X
.Childadded Observer Doesn't Get Called If The Function Is Not Called Explicitly. Swift 4
Tab Bar Item Icons Appear Darker
Osx/Swift: Call a Function When Internet Connection Is Established/Restored
Watchkit Extension Cannot Read from Icloud
Not Getting Screenlock Notification on Swift 4 on Mac