Enhanced scripting support for Kotlin on *nix-based systems.
Kotlin has some built-in support for scripting already but it is not yet feature-rich enough to be a viable alternative
in the shell.
In particular this wrapper around kotlinc adds
Compiled script caching (using md5 checksums)
Dependency declarations using gradle-style resource locators and automatic dependency resolution
with jcabi-aether
More options to provide scripts including interpreter mode, reading from stdin, local files or URLs
Embedded configuration for Kotlin runtime options
Support library to ease the writing of Kotlin scriptlets
Deploy scripts as stand-alone binaries
Taken all these features together, kscript provides an easy-to-use, very flexible, and almost zero-overhead solution
to write self-contained mini-applications with Kotlin.
Good News: Kotlin v1.4 finally ships
with a much improved - and needed - scripting integration.
See here for examples and
documentation. Still, we think that kscript has various benefits compared this new platform-bundled improved
toolstack, so we'll plan to support kscript until the kotlin platform will ship with an even more rich and versatile
kotlin scripting interpreter.
Once Kotlin is ready, you can install kscript with
sdk install kscript
To test your installation simply run
kscript --help
This will check and inform about updates. To update kscript simply install it again as described above.
Run with docker
We provide an executable docker container to run kscript
# using the latest version of kscript
docker run -i holgerbrandl/kscript 'println("Hello, world!")'# or using versioned container
docker run -i holgerbrandl/kscript:2.9.3 'println("Hello, world!")'
To use a script file outside of the container as input, you could do
docker run -i holgerbrandl/kscript - < script.kts
This will make kscript read the code from stdin while piping the file. Beware that the -i flag is needed to have
stdout redirected outside the container.
Please note, that currently @Include are not supported when using a dockerized kscript. Also, any resource outside the
container context may not be resolved correctly. To overcome this limitation, you could use for
instance bind mounts.
Installation without sdkman
If you have Kotlin and Maven already and you would like to install the latest kscript release without using sdkman
you can do so by unzipping the latest binary release. Don't
forget to update your $PATH accordingly.
Installation with Homebrew
On MacOS you can install kscript also with Homebrew
To build kscript yourself, simply clone the repo and do
./gradlew assemble
## Run kscript from output dir
./build/libs/kscript
Script Input Modes
The main mode of operation is kscript <script>.
The <script> can be a Kotlin *.kts script file , a script URL, - for stdin, a process substitution file handle,
a *.kt source file with a main method, or some kotlin code.
Interpreter Usage
To use kscript as interpreter for a script just point to it in the shebang line of your Kotlin scripts:
#!/usr/bin/env kscript
println("Hello from Kotlin!")
for (arg in args) {
println("arg: $arg")
}
Inlined Usage
To use kscript in a workflow without creating an additional script file, you can also use one of its supported modes
for inlined usage. The following modes are supported:
Directly provide a Kotlin scriptlet as argument
kscript 'println("hello world")'
Pipe a Kotlin snippet into kscript and instruct it to read from stdin by using - as script argument
echo '
println("Hello Kotlin.")
' | kscript -
Using heredoc (preferred solution for inlining) which gives you some more flexibility to also use single quotes in
your script:
kscript - <<"EOF"
println("It's a beautiful day!")
EOF
Since the piped content is considered as a regular script it can also have dependencies
Finally (for sake of completeness), it also works with process substitution and for sure you can always provide
additional arguments (exposed as args : Array<String> within the script)
Inlined kscripts are also cached based on md5 checksum, so running the same snippet again will use a cached jar (
sitting in ~/.kscript).
URL usage
To support remote scriplet repositories, kscript can also work with URLs. Consider the
following hello-world-gist-scriptlet
which is hosted on github (but any URL would work). To run it locally as a tool simply refer to it (here using the
shortened raw-URL of the
script for better readability)
kscript https://git.io/v1cG6 my argu ments
To streamline the usage, the first part could be even aliased:
alias hello_kscript="kscript https://git.io/v1cG6"
hello_kscript my argu ments
Via this mechanism, kscript allows for easy integration of remotely hosted (mini) programs into data workflows.
URL-scripts are cached locally to speed up processing, and kscript --clear-cache can be used to wipe the cache if
needed.
See this blogpost for a more extensive
overview about URL support in kscript.
Script Configuration
The following directives supported by kscript to configure scripts:
//DEPS to declare dependencies with gradle-style locators
//KOTLIN_OPTS to configure the kotlin/java runtime environment
//INCLUDE to source kotlin files into the script
//ENTRY to declare the application entrypoint for kotlin *.kt applications
Declare dependencies with //DEPS
To specify dependencies simply use gradle-style locators. Here's an example
using docopt and log4j
#!/usr/bin/env kscript
//DEPS com.offbytwo:docopt:0.6.0.20150202,log4j:log4j:1.2.14importorg.docopt.Docoptimportjava.util.*val usage ="""Use this cool tool to do cool stuffUsage: cooltool.kts [options] <igenome> <fastq_files>...Options: --gtf <gtfFile> Custom gtf file instead of igenome bundled copy --pc-only Use protein coding genes only for mapping and quantification"""val doArgs =Docopt(usage).parse(args.toList())
println("Hello from Kotlin!")
println("Parsed script arguments are: \n"+ doArgs)
kscript will read dependencies from all lines in a script that start with //DEPS (if any). Multiple dependencies can
be split by comma, space or semicolon.
Configure the runtime with //KOTLIN_OPTS
kscript allows to provide a //KOTLIN_OPTS directive followed by parameters passed on to kotlin similar to how
dependencies are defined:
#!/usr/bin/env kscript
//KOTLIN_OPTS -J-Xmx5g -J-serverprintln("Hello from Kotlin with 5g of heap memory running in server mode!")
Note: Similar to the runtime you can also tweak the compile step by providing //COMPILER_OPTS.
Ease prototyping with //INCLUDE
kscript supports an //INCLUDE directive to directly include other source files without prior compilation. Absolute
and relative paths, as well as URLs are supported. Example:
The argument can be an URL, absolute or relative file path. Note that URLs used in include directives are cached locally
to speed up processing, that is kscript won't fetch URLs again unless the user actively clears the cache
with kscript --clear-cache.
To do so kscript supports annotations to be used instead of
comment directives:
#!/usr/bin/env kscript
// Declare dependencies
@file:DependsOn("com.github.holgerbrandl:kutils:0.12") @file:DependsOn(
"com.beust:klaxon:0.24",
"com.github.kittinunf.fuel:fuel:2.3.1"
)
// To use a custom maven repository you can declare it with
@file:MavenRepository("imagej-releases", "http://maven.imagej.net/content/repositories/releases")
// For compatibility with https://github.com/ligee/kotlin-jupyter kscript supports also
@file:DependsOnMaven("net.clearvolume:cleargl:2.0.1")
// Note that for compatibility reasons, only one locator argument is allowed for @DependsOnMaven// also protected artifact repositories are supported, see <https://github.com/holgerbrandl/kscript/blob/master/test/TestsReadme.md#manual-testing>// @file:MavenRepository("my-art", "http://localhost:8081/artifactory/authenticated_repo", user="auth_user", password="password")// You can use environment variables for user and password when string surrounded by double {} brackets // @file:MavenRepository("my-art", "http://localhost:8081/artifactory/authenticated_repo", user="{{ARTIFACTORY_USER}}", password="{{ARTIFACTORY_PASSWORD}}")// will be use 'ARTIFACTORY_USER' and 'ARTIFACTORY_PASSWORD' environment variables// if the value doesn't found in the script environment will fail// Include helper scripts without deployment or prior compilation
@file:Include("util.kt")
// Define kotlin options
@file:KotlinOpts("-J-Xmx5g") @file:KotlinOpts("-J-server") @file:CompilerOpts("-jvm-target 1.8")
// declare application entry point (applies on for kt-files)
@file:EntryPoint("Foo.bar")
print("1+1")
To enable the use of these annotations in Intellij, the user must add the following artifact to the project
dependencies:
com.github.holgerbrandl:kscript-annotations:1.2
kscript will automatically detect an annotation-driven script, and if so will declare a dependency on this artifact
internally.
Note, that if a script is located in a package other than the root package, you need to import the annotations with (
e.g. import DependsOn).
Text Processing Mode
kscript can be used as a speedier and more flexible substitute for built-in terminal text tools such as awk or sed
. Its text processing mode can be enabled with -t or --text. If so, kscript will
Declare com.github.holgerbrandl:kscript-support-api:1.2.5 as dependency for the script.
This support library eases the writing of Kotlin scriptlets for
text-processing. It includes solutions to common use-cases like argument parsing, data streaming, IO utilities, and
various iterators to streamline the writing of scriptlets for text processing.
Import the kscript.* namespace
Define variable val lines = kscript.text.resolveArgFile(args) which returns an iterator over the lines in the first
input argument of the script, or the standard input if no file arguments are provided to the script
This allows to to replace awkward constructs (or sed orperl) with kotlinesque solutions such as
In this example, the extension
method Iterable<String>.print()
to print the lines to stdout comes from the support API. The rest is stdlib Kotlin.
For more examples using the support library see
this blog post.
Treat yourself a REPL with --interactive
To create an interactive kotlin shell (
aka REPL) with all script dependencies added
to the classpath you can use --interactive.
For example, let's assume the following short script, named CountRecords.kts
#!/usr/bin/env kscript
@file:DependsOn("com.github.holgerbrandl:kutils:0.12")
importde.mpicbg.scicomp.bioinfo.openFastaif (args.size !=1) {
System.err.println("Usage: CountRecords <fasta>")
kotlin.system.exitProcess(-1)
}
val records = openFasta(java.io.File(args[0]))
println(records.count())
To build a REPL that has the declared artifact in its classpath, we can just do
kscript --interactive CountRecords.kts
which will bring up the classpath-enhanced REPL:
Creating REPL from CountRecords.kts
Welcome to Kotlin version 1.1.51 (JRE 1.8.0_151-b12)
>>> import de.mpicbg.scicomp.bioinfo.openFasta
>>>
Boostrap IDEA from a kscriptlet
Artifacts and versions will differ between scripts, so it is hard to maintain them all in a single project. To
nevertheless provide optimal tooling when scripting with Kotlin kscript allows to create temporary projects
for <script> arguments. .
kscript --idea CountRecords.kts
This will open IntelliJ IDEA with a minimalistic project containing just your (
<script> and (2) a generated gradle.build file:
This assumes that you have the Intellij IDEA command line launcher idea in your PATH. It can be created in IntelliJ
under Tools -> Create Command-line Launcher or you can set the command used to launch your intellij
as KSCRIPT_IDEA_COMMAND env property
Deploy scripts as standalone binaries
To deploy a script simply do
kscript --package some_script.kts
./some_script --arg u ments
The created binary will contain a compiled copy of the script, as well as all declared dependencies (fatjar). Also
runtime jvm parameters declared via @file:KotlinOpts are used to spin up the JVM.
Just java is required to run these binaries.
Embed kscript installer within your script
To make a script automatically install kscript and its dependencies on first run if necessary, run:
kscript --add-bootstrap-header some_script.kts
Now some_script.kts can be shared and run directly on any other machine that has bash, without having to go through
the Installation steps first.
Note that unlike the --package option this doesn't produce a separate file,
allowing the distributed script to be read and modified(including
with kscript --idea) similar to what you might expect with bash/python/ruby
scripts.
On the other hand this doesn't embed dependencies within the script("fat jar"), so internet connection may be required
on its first run.
Why is kscript not calling the main method in my .kts script?
There is no need for
a main method in a Kotlin script. Kotlin *.kts scripts can be more simplistic compared to more common kotlin *.kt
source files. The former work without a main method by directly running the provided code from top to bottom. E.g.
print("hello kotlin!")
is a valid Kotlin kts script. Plain and simple, no main, no companion, just a few bits of code.
Does kscript also work for regular kotlin .kt source files with a main as entry point?
Yes, (since v1.6) you can run kotlin source files through kscript. By default it will assume a top-level main method
as entry-point.
However in case you're using a companion object to declare the entry point, you need to indicate this via the //ENTRY
/@file:Entry directive:
Why does it fail to read my script file when using cygwin?
In order to use cygwin you need to use windows paths to provide your scripts. You can map cygwin paths using cygpath.
Example
What are performance and resource usage difference between scripting with kotlin and python?
Kotlin is a compiled language, so there is a compilation overhead when you run a script/application written in Kotlin
for the first time.
Kotlin runs (mainly) on the JVM which needs some time (~200ms) to start up. In contrast, the python interpreter has
close to zero warmup time.
I think there is a consensus that JVM programs execute much faster than python equivalents. Still, python might be
faster depending on your specific usecase. Also, with kotlin-native becoming more mature, you could compile into native
binaries directly, which should bring it close to C/C++ performance.
Main motivations for using Kotlin over Python for scripting and development are
Kotlin is the better designed, more fluent language with much better tooling around it
The JVM dependency ecosystem allows for strict versioning. No more messing around with virtualenv, e.g. to run a short
10liner against a specific version of numpy.
Does kscript work with java?
The only language supported by kscript is kotlin. For a similar approach centering around Java scripting
see jbang.