A package for creating, rendering, implementing and running dynamic shell script templates in Swift with leaf-inspired syntax.
Modify shell scripts dynamically with Swift and use shell script functions in your swift code.
- use leaf-inspired syntax tags such as #(var), #func(Input) and custom ones to create dynamic shell script templates.
- export the rendered script or run it directly
- embed shell script functions in swift code
- automatically index and embed shell scripts from the bundle or a folder
- run scripts and commands with a timeout handler
- get and modify file infos
- Shortoverview
- Installation
- Create dynamic script templates
- Render scripts
- Run any shell script and command
- Embed shell script functions in swift
- Get and modify file info
shell script:
#!/bin/sh
echo "this is a dynamic shell script"
ls /Documents/testFileName-#(test).txt // variable test
touch fileWithCreationDate-#getDate(yyyy-MM-dd).txt // function getDate with input "yyyy-MM-dd"
§§hi // custom tag §§hi
exit
in swift:
import SwiftyShellScript
/* rendering scripts */
let a = Item(identifier: "test", input: "this is a test variable", taskType: .variable)
let b = Item(identifier: "§§hi", input: "this is a custom tag", taskType: .custom)
let c = Item(identifier: "getDate", function: { input in input.doSomething() }, taskType: .function)
let renderSet = [a, b, c]
let script = shellScriptRenderer("/Users/admin/Documents/test.sh")
script.render(renderSet)
// export the rendered script
script.exportTo("/Users/admin/Documents/renderedTest.sh")
script.chmod(to: 755, .int)
// run the rendered script automatically in tmp dir
script.timeout = 300 // if the script is not done in 5 min it will be killed
let output = script.runScript()
print(output.standardOutput)
shell script:
# /* SwiftyShellScript Bridge
#
# - function: greet
#
# */End
#!/bin/sh
greet() {
echo "Hi $1"
}
"$@"
exit
in swift:
ShellScripts("pathToScriptFolder").function("greet", param: "Thomas") // returns "Hi Thomas"
// add a timeout
ShellScripts("pathToScriptFolder").function("greet", param: "thomas", timeout: 20) // returns "Hi Thomas"
/* working with files */
let info = fileInfo("/Users/admin/Documents/test.sh")
info.fileSize(.byte))
info.groupOwnerAccountName()
info.posixPermissions(as: .int)
info.ownerAccountName()
...
info.isWritable()
info.isSymbolicLink()
// modify
let file = modify("/Users/admin/Documents/test.sh")
file.rename(to: renamed.txt)
file.chmod(755, .int)
Add SwiftyShellScript as a dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/gregorFeigel/SwiftyShellScript.git", from: "0.0.3") // .branch("main")
]
With the leaf-inspired syntax you can easily create dynamic script templates. The three tag types (var, func and custom) are explained below:
A variable is defined by using #(yourVarName) in your shell script.
#!/bin/sh
#(hello) // variable hello
touch test-#(fileName).txt // variable fileName
exit
To initiate your variable, create a render element with the task type .variable:
The variable name in this example is "hello" and will be replaced with "this is a test variable".
let a = Item(identifier: "hello", input: "#this is a test variable", taskType: .variable)
let b = Item(identifier: "fileName", input: "\(NSUserName()!)", taskType: .variable)
rendered result:
#!/bin/sh
#this is a test variable
touch test-admin.txt
exit
A function is defined by using #yourFunction(functionInput) in your shell script.
#!/bin/sh
echo "#getDate(yyyy-MM-dd HH:mm:ss)" // function "getDate" with input "yyyy-MM-dd HH:mm:ss"
exit
To initiate your function, create a render element with the task type .function:
The function name in this example is "getDate" and will be replaced with the result (String) of your function you define in the funtion init.
let a = Item(identifier: "getDate", function: { input in input.getDate() }, taskType: .function)
extension String {
func getDate() -> String {
let dateFormat = DateFormatter()
dateFormat.dateFormat = self
return dateFormat.string(from: Date()) + " - input format:" + self
}
}
rendered result:
#!/bin/sh
echo "2021-06-24 10:34:04 - input format: yyyy-MM-dd HH:mm:ss"
exit
With the custom tag you can define anything and replace it with the input parameter.
#!/bin/sh
echo "§§hi" // custom tag §§hi
exit
To initiate your custom tag, create a render element with the task type .custom:
The custom tag in this example is "§§hi" and is replaced by "this is a custom tag".
let a = Item(identifier: "§§hi", input: "this is a custom tag", taskType: .custom)
rendered result:
#!/bin/sh
echo "this is a custom tag"
exit
To render the script, you need to create a render set and then pass the render set into the renderer:
// Create the render set by simply adding the individual elements together into an array:
let a = Item(identifier: "test", input: "this is a test variable", taskType: .variable)
let b = Item(identifier: "test2", input: "this is a second test variable", taskType: .variable)
let renderSet = [a, b])
// select the script
let script = shellScriptRenderer("/Users/admin/Documents/test.sh")
// render the script with the render set
script.render(renderSet)
Export the rendered script to a new path. With the options .force and .sensitive you can decide whether the export job should overwrite the file, if it already exists or not. It is set to .sensitive by default.
let script = shellScriptRenderer("/Users/admin/Documents/test.sh")
script.render(renderSet)
script.exportTo("/Users/admin/Documents/renderedTest.sh")
// force overwriting
script.exportTo("/Users/admin/Documents/renderedTest.sh", overwrite: .force)
// sensitive
script.exportTo("/Users/admin/Documents/renderedTest.sh", overwrite: .sensitive)
/* directly change posix permissions */
// input as int
script.chmod(to: 755, .int)
//input as octal number
script.chmod(to: 0o755, .octalNumber)
//input as octal number
script.chmod(to: 493, .octalNumber)
Run the rendered script directly after rendering. The timeout option will force terminate the process if the timeout is reached. The timeout value is set to 300 sec by default. If you want to disable it, you can set the time to .infinity.
let script = shellScriptRenderer("/Users/admin/Documents/test.sh")
script.render(renderSet)
script.timeout = 60 // time in seconds
script.launchPath = "/bin/sh" // by default set to /bin/bash
script.arg = "-e" // by default set to -c
let output = script.runScript()
print(output.processTime) // Double - time the process took to complete
print(output.timeoutInterrupt) // Bool - true if timeout terminated the process
print(output.error) // String - error while launching the process
print(output.terminationStatus) // Int32 - process termination status
// shell script output split into standard error and standard output
print(output.standardError)
print(output.standardOutput)
Run script and commands and get all informations back.
let script = runScript(scriptPath: "/Users/admin/Documents/test.sh")
script.timeout = 30
script.launchPath = "/bin/sh" // by default set to /bin/bash
script.arg = "-e" // by default set to -c
let output = script.runDefault()
print(output.processTime) // Double - time the process took to complete
print(output.timeoutInterrupt) // Bool - true if timeout terminated the process
print(output.error) // String - error while launching the process
print(output.terminationStatus) // Int32 - process termination status
// shell script output split into standard error and standard output
print(output.standardError)
print(output.standardOutput)
Run script and commands and get the output printed in realtime. ! This does only print the output and returns nothing !
let script = runScript(scriptPath: "/Users/admin/Documents/test.sh")
script.timeout = 30
script.launchPath = "/bin/sh" // by default set to /bin/bash
script.arg = "-e" // by default set to -c
script.shellPrintRealTime()
Run script and commands. If the standard error is empty the return value is true
let script = runScript(scriptPath: "/Users/admin/Documents/test.sh")
script.timeout = 30
script.launchPath = "/bin/sh" // by default set to /bin/bash
script.arg = "-e" // by default set to -c
let output = script.shellBool()
print(output)
Run script and commands and only get the standard error output.
let script = runScript(scriptPath: "/Users/admin/Documents/test.sh")
script.timeout = 30
script.launchPath = "/bin/sh" // by default set to /bin/bash
script.arg = "-e" // by default set to -c
let output = script.shellErrorsOnly()
print(output)
Easily use your shell script functions inside swift.
To use shell script functions in swift you need to create a bridge header in your shell script, that registers the public available functions from your script.
! The function names must be unique. !
# /* SwiftyShellScript Bridge
#
# - function: greet //the name of the function
# - function: getData //the name of the function
#
# */End
If you have an app with a bundle, just create in your app bundle a folder named "shellScripts" and move all your scripts in there - you can use sub folders as well. All the scripts with the extension .sh in this folder will be automatically indexed and the function addressed to the correct script.
let result = ShellScripts().function("greet", param: "Thomas")
If your project has no bundle, just insert the folder dir of the folder containing all your scripts.
let result = ShellScripts(URL(fileURLWithPath: "/Users/admin/Scripts")).function("greet", param: "Thomas")
shell script:
# /* SwiftyShellScript Bridge
#
# - function: greet
# - function: say
#
# */End
#!/bin/bash
function greet()
{
echo "Hi $1"
}
say()
{
echo "$1"
}
"$@"
exit
in swift:
var result = ShellScripts().function("greet", param: "Thomas") // returns Hi Thomas
result = ShellScripts().function("say", param: "\"this is a test\"") // returns this is a test
get file info: return values are optional - returns nil if files does not exist, or the value can't be read.
let info = fileInfo("/Users/admin/Documents/test.sh")
info.isExecutable() // -> Bool?
info.isReadable() // -> Bool?
...
info.ownerAccountName() //-> String?
modify file info:
let info = modify("/Users/admin/Documents/test.sh")
info.chmod(755, .int)
info.rename(to: "")