Command-Line Scripts and Automation
Prerequisites: Files and I/O, Using Java libraries
Groovy is an excellent language for operations scripts: it has real data structures, JSON/XML/YAML parsing, and every Java library — without a build step.
Parsing options with CliBuilder
import groovy.cli.commons.CliBuilder
def cli = new CliBuilder(usage: 'backup [options] <dirs>')
cli.with {
h(longOpt: 'help', 'Show usage')
v(longOpt: 'verbose', 'Print every file')
d(longOpt: 'dest', args: 1, argName: 'dir', 'Destination folder')
k(longOpt: 'keep', args: 1, type: Integer, defaultValue: '7', 'Backups to keep')
}
def options = cli.parse(['--verbose', '-d', '/mnt/backup', 'docs', 'photos'])
println "verbose: ${options.v}"
println "dest: ${options.d}"
println "keep: ${options.k}"
println "dirs: ${options.arguments()}"
verbose: true
dest: /mnt/backup
keep: 7
dirs: [docs, photos]
In a real script, pass args instead of a literal list, and call
cli.usage() — which prints every option with its description — when
options.h is set or parsing fails. Groovy ships two implementations with
the same API: groovy.cli.commons (Apache Commons CLI, used here) and
groovy.cli.picocli (which needs the picocli jar on the classpath).
Running other programs
// illustrative — runs external commands
def proc = ['git', 'log', '--oneline', '-3'].execute()
def out = new StringBuilder(), err = new StringBuilder()
proc.consumeProcessOutput(out, err)
proc.waitFor()
if (proc.exitValue() != 0) {
System.err.println "git failed: $err"
System.exit(1)
}
out.eachLine { println "commit: $it" }
// run in a folder with extra environment variables (Groovy 6)
def build = ['./gradlew', 'test'].execute(dir: 'service', env: [CI: 'true'])
build.inputStream.eachLine { println it }
println "exit code: ${build.waitFor()}"
.execute()on a list starts a process — use a list rather than a single string so arguments with spaces are passed correctly.- Always consume the output (or the process can block when its buffer fills) and check the exit code.
Working with YAML, TOML, and CSV
import groovy.yaml.YamlSlurper
import groovy.yaml.YamlBuilder
def yaml = '''
services:
- name: api
replicas: 3
- name: worker
replicas: 1
'''
def conf = new YamlSlurper().parseText(yaml)
println conf.services.collect { "$it.name x$it.replicas" }
println conf.services.sum { it.replicas }
def out = new YamlBuilder()
out(environment: 'prod', replicas: conf.services*.replicas)
println out.toString().trim()
[api x3, worker x1]
4
---
environment: "prod"
replicas:
- 3
- 1
groovy-yaml and groovy-toml (and, in Groovy 6, groovy-csv) follow
the same slurper/builder pattern as JSON. Validate YAML with the
YAML Validator.
Calling an HTTP API
// illustrative — needs network access
import groovy.json.JsonSlurper
import java.net.http.*
def client = HttpClient.newHttpClient()
def request = HttpRequest.newBuilder(URI.create('https://api.github.com/repos/apache/groovy'))
.header('Accept', 'application/json')
.build()
def response = client.send(request, HttpResponse.BodyHandlers.ofString())
def repo = new JsonSlurper().parseText(response.body())
println "${repo.full_name}: ${repo.stargazers_count} stars"
Java's HttpClient works well from Groovy. Groovy 6 also has an
incubating groovy-http-builder module with a small request DSL.
Script tips
- Start with
#!/usr/bin/env groovyandchmod +xon Unix. - Fail loudly: exit with a non-zero code on errors, so CI notices.
- Pin
@Grabversions, and cache grapes in CI. - Start-up takes a second or so; for scripts that run thousands of times
a day, compile them (
groovyc) or use a long-running service instead.