Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass parameters via NSUserUnixTask and then access via Script?

I'm working on a feature to enable user-provided scripts that will be executed by a Mac app.

NSUserScriptTask underlies the script-calling code, and the NSUserAppleScriptTask and NSUserAutomatorTask subclasses both allow the setting of variables to pass information from Swift to the script:

Passing variables to an AppleScript

Setting NSUserAutomatorTask variables without requiring Automator Workflows to declare that variable

That leaves NSUserUnixTask, which does not support setting variables. It instead supports a [String] array called arguments.

When executing the scripts, I will want to pass 3 variables from the Mac app:

let folderURL: String? = "/files/"
let fileURLs: [String] = ["/files/file1", "/files/file2"]
let selectionType: Int? = 1

let arguments: [String] = ["how", "should", "arguments", "be", "formatted", "?"]

let unixScript = try! NSUserUnixTask(url: url)
unixScript.execute(withArguments: arguments) { (error) in
    if let error = error {
        print(error)
    }
}

The 3 swift variables must be condensed into a single [String] array for NSUserUnixTask to use as its arguments parameter.

When the script runs, I want to then provide the script author access to the same arguments in a prototypical way:

#! /bin/bash 
say "How should the script then access/parse the arguments?"
say $@ #says the arguments

Based on ease of use for the script author, how should the Swift code format its information into the arguments [String]?

What boilerplate code could be provided to allow easy and pragmatic access to the parameters from the script?

like image 487
pkamb Avatar asked Oct 16 '22 12:10

pkamb


1 Answers

There are many ways to do it, depending on what the shell script programmers expect. If I were one of them I'll ask you to keep it simple:

  • position parameters (fixed number of mandatory parameters at the start)
  • optional named parameters (any parameter of the style -flag value)
  • more parameters (a variable number of additional parameters)

Following your example, slightly modified:

import Foundation

let shellScript = CommandLine.arguments[1]

let folderURL: String = "/files/"
let fileURLs: [String] = ["/files/file1", "/files/file2"]
let selectionType: Int? = 1

var arguments = [String]()

// script.sh <folder> [-type <type>] file1, file2, ...

arguments.append(folderURL)  // <folder> is mandatory

if let type = selectionType {    // -type <type> is optional
    arguments.append("-type")   
    arguments.append("\(type)")
}

arguments += fileURLs  // file1, ... (if it can't be empty check it here)

assert(FileManager.default.fileExists(atPath: shellScript))
let unixScript = try! NSUserUnixTask(url: URL(fileURLWithPath: shellScript))
let stdout = FileHandle.standardOutput
unixScript.standardOutput = stdout

unixScript.execute(withArguments: arguments) { error in
    if let error = error {
        print("Failed: ", error)
    }
    exit(0)
}

dispatchMain()  // I'm not swift script expert, there may be a better way

And the corresponding shell script:

#!/bin/bash

# mandatory positional arguments

FOLDER=$1 && shift
# other mandatory arguments goes here

TYPE=7  # default type
FILES=()

while [[ $# -gt 0 ]]; do
    arg=$1
    case $arg in
        -type) 
            TYPE=$2 && shift && shift
            ;;
        # other named parameters here
        *)
            FILES+=($1) && shift
            ;;
    esac
done

echo FOLDER: '<'${FOLDER}'>'
echo TYPE: '<'${TYPE}'>'
echo FILES: '<'${FILES[@]}'>'

exit 0

Example:

ScriptRunner /path/to/script.sh

The output:

FOLDER: </files/>
TYPE: <1>
FILES: </files/file1 /files/file2>

I used swift package manager to build:

// swift-tools-version:4.2

import PackageDescription

let package = Package(
    name: "ScriptRunner",
    dependencies: [],
    targets: [
        .target(name: "ScriptRunner", dependencies: [])
    ]
)
like image 129
djromero Avatar answered Oct 20 '22 23:10

djromero