Scripts
scala-cli
accepts Scala scripts as files that end in .sc
.
Unlike .scala
files, in scripts, any kind of statement is accepted at the top-level:
val message = "Hello from Scala script"
println(message)
A script is run with the scala-cli
command:
scala-cli hello.sc
Hello from Scala script
The way this works is that a script is wrapped in an object
before it's passed to the Scala compiler, and a main
method is added to it.
In the previous example, when the hello.sc
script is passed to the compiler, the altered code looks like this:
object hello {
val message = "Hello from Scala script"
println(message)
def main(args: Array[String]): Unit = ()
}
The name hello
comes from the file name, hello.sc
.
When a script is in a sub-directory, a package name is also inferred:
def hello = "Hello from Scala scripts"
import constants.messages
println(messages.hello)
Please note: when referring to code from another script, the actual relative path from the project root is used for the
package path. In the example above, as messages.sc
is located in the my-app/constants/
directory, to use the hello
function you have to call constants.messages.hello
.
When referring to code from a piped script, just use its wrapper name: stdin
.
echo '@main def main() = println(stdin.message)' > PrintMessage.scala
echo 'def message: String = "Hello"' | scala-cli PrintMessage.scala _.sc
Hello
To specify a main class when running a script, use this command:
scala-cli my-app --main-class main_sc
Hello from Scala scripts
Both of the previous scripts (hello.sc
and main.sc
) automatically get a main class, so this is required to
disambiguate them. If a main class coming from a regular .scala
file is present in your app's context, that will be
run by default if the --main-class
param is not explicitly specified.
When in doubt, you can always list the main classes present in your app by passing --list-main-classes
.
echo '@main def main1() = println("main1")' > main1.scala
echo '@main def main2() = println("main2")' > main2.scala
echo 'println("on-disk script")' > script.sc
echo 'println("piped script")' | scala-cli --list-main-classes _.sc main1.scala main2.scala script.sc
stdin_sc script_sc main2 main1
Self executable Scala Script
You can define a file with the “shebang” header to be self-executable. Please remember to use scala-cli shebang
command, which makes scala-cli
compatible with Unix shebang interpreter directive. For example, given this script:
#!/usr/bin/env -S scala-cli shebang
println("Hello world")
You can make it executable and run it, just like any other shell script:
chmod +x HelloScript.sc
./HelloScript.sc
Hello world
It is also possible to set scala-cli
command-line options in the shebang line, for example
#!/usr/bin/env -S scala-cli shebang --scala-version 2.13
Arguments
You may also pass arguments to your script, and they are referenced with the special args
variable:
#!/usr/bin/env -S scala-cli shebang
println(args(1))
chmod +x p.sc
./p.sc hello world
world
Define source files in using directives
You can also add source files with the using directive //> using file
in Scala scripts:
//> using file "Utils.scala"
println(Utils.message)
object Utils {
val message = "Hello World"
}
scala-cli
takes into account and compiles Utils.scala
.
scala-cli main.sc
# Hello World
Difference with Ammonite scripts
Ammonite is a popular REPL for Scala that can also compile and run .sc
files.
scala-cli
and Ammonite are similar, but differ significantly when your code is split in multiple scripts:
- In Ammonite, a script needs to use
import $file
directives to use values defined in another script - With
scala-cli
, all scripts passed can reference each other without such directives
On the other hand:
- You can pass a single "entry point" script as input to Ammonite, and Ammonite finds the scripts it depends on via
the
import $file
directives scala-cli
requires all scripts to be passed beforehand, either one-by-one, or by putting them in a directory, and passing the directory toscala-cli