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
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.
What Function in swift 5 that runs/exec an external swift script?
In fact, there is a library that can run external codes in swift Similar like your python Example, you can download the Library by github:
https://github.com/samuelmeuli/swift-exec
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
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 run and print zsh commands in Swift Executables (like vi)
Turns out, you can use the system()
function for C++ in Swift!
First, I created a new target in my package (to get past language mixing errors):
.target(name: "CBridge", dependencies: []),
Then, in the target's source folder, I put the following files:
CBridge.cpp
include/CBridge.h
include/CBridge-Bridging-Header.h
CBridge.cpp
#include "include/CBridge.h"
#include <iostream>
using namespace std;
void shellCMD(const char *cmd) {
system(cmd);
}
CBridge.h
#ifdef __cplusplus
extern "C" {
#endif
void shellCMD(const char *cmd);
#ifdef __cplusplus
}
#endif
CBridge-Bridging-Header.h
#include "CBridge.h"
Simply call shellCMD(command)
and it will run it, just like 'system(command)'!
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
How to launch a terminal app on PATH in Swift on macOS?
A comment from @MartinR brought me to the right idea. Don't run the terminal command directly, but from a new shell. This way it will do the PATH resolution for you:
let shellProcess = new Process();
shellProcess.launchPath = "/bin/bash";
shellProcess.arguments = [
"-l",
"-c",
// Important: this must all be one parameter to make it work.
"mysqlsh --py -e 'print(\"Call from shell\")",
];
shellProcess.launch();
This example uses the MySQL shell as example, which is in /usr/local/bin
(unlike git, which is in /usr/bin
). Git worked already in the beginning, while mysqlsh did not. From the comment you can also see that it is important to make the mysqlsh call a complete and single parameter entry. If you split that then /bin/bash -c
will only execute mysqlsh
and not pass on the given shell parameters.
Related Topics
How to Resolve "Ambiguous Use Of" Compile Error With Swift #Selector Syntax
How to Set Associated Objects in Swift
Opt Out of Uiscenedelegate/Swiftui on Ios
Access Firebase Variable Outside Closure
Print Without Newline in Swift
Deletable Table With Textfield on Swiftui
Nsfilemanager Fileexistsatpath:Isdirectory and Swift
How to Return an Object That I Create in My Data Service Class Through Firebase
How to Convert a Date String With Optional Fractional Seconds Using Codable in Swift
How to Test Equality of Swift Enums With Associated Values
Photopicker Discovery Error: Error Domain=Pluginkit Code=13
Getting "File Not Found" in Bridging Header When Importing Objective-C Frameworks into Swift Project