System Command Execution (exec)
The Golo exec module allows you to execute external system commands similar to Go’s os/exec package, with idiomatic Golo error handling using exceptions and Result types.
Overview
The exec module provides an interface for executing system commands and capturing their output. It follows the idiomatic Golo approach:
- Exceptions for imperative error handling (try/catch)
- Result types for functional error handling
Main Function
execCommand(commandName, args...)
Creates a Command object for executing an external command.
Parameters:
commandName(String): The name of the command to execute (e.g., “ls”, “git”, “echo”)args...(String): Optional arguments to pass to the command
Returns:
- A
Commandobject that can be passed toexecCombinedOutput()
Example:
let cmd = execCommand("ls", "-la", "/tmp")
execCombinedOutput(cmd)
Executes the command and returns combined output (stdout + stderr).
Parameters:
cmd: The Command object created withexecCommand()
Returns:
String: The command output on success- Throws an
Exceptionon error
Example with try/catch:
try {
let output = execCombinedOutput(execCommand("ls", "-la"))
println(output)
} catch (e) {
println("Error:", e)
}
Example with Result (functional):
import gololang.Errors
let result = trying(-> execCombinedOutput(execCommand("whoami")))
either(result,
|output| -> println("User:", output: trim()),
|err| -> println("Error:", err)
)
Error Handling
Imperative approach: try/catch
try {
let cmd = execCommand("ls", "/tmp")
let output = execCombinedOutput(cmd)
println("Success:", output)
} catch (e) {
println("Error:", e)
}
Functional approach: Result type
import gololang.Errors
# Wrapper to get a Result
let result = trying(-> execCombinedOutput(execCommand("pwd")))
# Check the type
if isOk(result) {
println("Output:", result: value())
} else {
println("Error:", result: message())
}
# Or use either()
either(result,
|output| -> println("Success:", output),
|err| -> println("Error:", err)
)
Functional Approach with Result
Helper functions
import gololang.Errors
# Execute and return a Result
function execResult = |cmd| {
return trying(-> execCombinedOutput(cmd))
}
# Execute and return output or empty string
function execQuiet = |cmd| {
let result = trying(-> execCombinedOutput(cmd))
return orElse(result, "")
}
Composition with mapResult
let result = trying(-> execCombinedOutput(execCommand("date")))
let upperDate = mapResult(result, |output| -> output: toUpperCase())
either(upperDate,
|output| -> println(output),
|err| -> println("Error:", err)
)
Chaining with flatMapResult
let pipeline = flatMapResult(
execResult(execCommand("whoami")),
|user| {
return flatMapResult(
execResult(execCommand("pwd")),
|dir| {
return Result(user + " @ " + dir)
}
)
}
)
either(pipeline,
|info| -> println(info),
|err| -> println("Error:", err)
)
Examples
Example 1: Simple command with try/catch
#!/usr/bin/env golo
module examples.SimpleCommand
function main = |args| {
try {
let output = execCombinedOutput(execCommand("ls", "-la", "/tmp"))
println(output)
} catch (e) {
println("Error:", e)
}
}
Example 2: Error handling with Result
#!/usr/bin/env golo
module examples.ResultStyle
import gololang.Errors
function main = |args| {
let result = trying(-> execCombinedOutput(execCommand("whoami")))
either(result,
|output| -> println("User:", output: trim()),
|err| -> println("Error:", err)
)
}
Example 3: Collecting multiple commands
import gololang.Errors
function main = |args| {
let commands = list[
execCommand("pwd"),
execCommand("whoami"),
execCommand("date")
]
let results = commands
: map(|cmd| -> trying(-> execCombinedOutput(cmd)))
: filter(|result| -> isOk(result))
: map(|result| -> result: value(): trim())
foreach output in results {
println(output)
}
}
Functional Patterns
Filter
Filter successful commands:
let commandsToCheck = list["git", "docker", "node", "nonexistent"]
let available = commandsToCheck
: map(|cmd| -> trying(-> execCombinedOutput(execCommand("which", cmd))))
: filter(|result| -> isOk(result))
: map(|result| -> result: value(): trim())
Map transformation
let dateResult = trying(-> execCombinedOutput(execCommand("date")))
let formatted = mapResult(dateResult, |output| {
return output: toUpperCase(): trim()
})
Chaining (flatMap)
let info = flatMapResult(
trying(-> execCombinedOutput(execCommand("whoami"))),
|user| {
return mapResult(
trying(-> execCombinedOutput(execCommand("hostname"))),
|host| -> user: trim() + "@" + host: trim()
)
}
)
Lazy evaluation
let lazyCommand = |cmd| -> || {
let result = trying(-> execCombinedOutput(cmd))
return orElse(result, "")
}
let lazyDate = lazyCommand(execCommand("date"))
# Execute only when called
println(lazyDate())
Best Practices
- Use Result for functional code: Prefer
trying()andeither()for a functional style - Try/catch for imperative code: Use try/catch for simple procedural style
- Clean output: Use
trim()to remove whitespace and newlines - Compose with helpers: Create reusable
execResult()andexecQuiet()functions - Secure handling: Avoid executing commands built from unvalidated user input
Useful Result Functions
trying(block)- Converts exceptions to ResultisOk(result)- Checks if Result is OkisError(result)- Checks if Result is Erroreither(result, okFn, errFn)- Pattern matching on ResultmapResult(result, fn)- Transforms the value if OkflatMapResult(result, fn)- Chains operations returning ResultorElse(result, default)- Returns value or default
Important Notes
- Use
list[...]for collections (supportsmap,filter) - Use
foreach item in collection {}to iterate trying(-> ...)is shorthand fortrying(|| -> ...)- Always call
trim()on outputs to remove newlines