Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to execute terminal command in swift?

I am new toSswift. How can I run this process from Swift code?

  1. open Terminal window
  2. execute cd Desktop/firebase-mac
  3. execute npm start

What I am actually trying to do is to start Node server on click from Swift code.

like image 265
aharo vishinsky Avatar asked Dec 11 '22 12:12

aharo vishinsky


2 Answers

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 argument date)
  • and the /usr/bin/env finds the command date
  • 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
like image 50
jm666 Avatar answered Dec 28 '22 18:12

jm666


You can just use the following code snippet, e.g., run("npm start"). It will execute the command on the default shell /bin/sh and return the output as a String.

func run(_ cmd: String) -> String? {
    let pipe = Pipe()
    let process = Process()
    process.launchPath = "/bin/sh"
    process.arguments = ["-c", String(format:"%@", cmd)]
    process.standardOutput = pipe
    let fileHandle = pipe.fileHandleForReading
    process.launch()
    return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
}

If you want to use a different bash then just replace the launchPath, e.g., for Zsh it would be /bin/zsh.

like image 20
Jens Meder Avatar answered Dec 28 '22 18:12

Jens Meder