Table of Contents
This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 14.12, “How to prompt users for input from Scala shell scripts.”
Problem
You want to prompt a user for input from a Scala shell script and read their responses.
Solution
In Scala 2, use the readLine
, print
, printf
, and Console.read*
methods to read user input, as demonstrated in the following script. Comments in the script describe each method:
#!/bin/sh exec scala "$0" "$@" !# // write some text out to the user with Console.println Console.println("Hello") // Console is imported by default, so it's not really needed, just use println println("World") // readLine lets you prompt the user and read their input as a String val name = readLine("What's your name? ") // readInt lets you read an Int, but you have to prompt the user manually print("How old are you? ") val age = readInt() // you can also print output with printf println(s"Your name is $name and you are $age years old.")
Update
As of Scala 2.11.0, readLine
is now available as scala.io.StdIn.readLine. The read* methods that I mention below are also in this package (such as scala.io.StdIn.readInt, etc.).
Discussion
The readLine
method lets you prompt a user for input, but the other read*
methods don’t, so you need to prompt the user manually with print
, println
, or printf
.
You can list the Console.read*
methods in the Scala REPL:
scala> Console.read readBoolean readByte readChar readDouble readFloat readInt readLine readLong readShort readf readf1 readf2 readf3
Be careful with the methods that read numeric values; as you might expect, they can all throw a NumberFormatException
.
Although these methods are thorough, if you prefer, you can also fall back and read input with the Java Scanner
class:
// you can also use the Java Scanner class, if desired val scanner = new java.util.Scanner(System.in) print("Where do you live? ") val input = scanner.nextLine() print(s"I see that you live in $input")
Reading multiple values from one line
If you want to read multiple values from one line of user input (such as a person’s name, age, and weight), there are several approaches to the problem.
To my surprise, I prefer to use the Java Scanner
class. The following code demonstrates the Scanner
approach:
import java.util.Scanner // simulated input val input = "Joe 33 200.0" val line = new Scanner(input) val name = line.next val age = line.nextInt val weight = line.nextDouble
To use this approach in a shell script, replace the input line with a readLine()
call, like this:
val input = readLine()
Of course if the input doesn’t match what you expect, an error should be thrown. The Scanner
class next*
methods throw a java.util.InputMismatchException
when the data doesn’t match what you expect, so you’ll want to wrap this code in a try/catch block.
I initially assumed that one of the readf
methods on the Console
object would be the best solution to this problem, but unfortunately they return their types as Any
, and then you have to cast them to the desired type. For instance, suppose you want to read the same name, age, and weight information as the previous example. After prompting the user, you read three values with the readf3
method like this:
val(a,b,c) = readf3("{0} {1,number} {2,number}")
If the user enters a string followed by two numbers, a result is returned, but if he enters an improperly formatted string, such as 1 a b
, the expression fails with a ParseException
:
java.text.ParseException: MessageFormat parse error! at java.text.MessageFormat.parse(MessageFormat.java:1010) at scala.Console$.readf(Console.scala:413) at scala.Console$.readf3(Console.scala:445)
Unfortunately, even if the user enters the text as desired, you still need to cast the values to the correct type, because the variables a
, b
, and c
are of type Any
. You can try to cast them with this approach:
val name = a val age = b.asInstanceOf[Long] val weight = c.asInstanceOf[Double]
Or convert them like this:
val name = a.toString val age = b.toString.toInt val weight = c.toString.toDouble
But for me, the Scanner is cleaner and easier.
A third approach is to read the values in as a String
, and then split them into tokens. Here’s what this looks like in the REPL:
scala> val input = "Michael 54 250.0" input: String = Michael 54 250.0 scala> val tokens = input.split(" ") tokens: Array[String] = Array(Michael, 54, 250.0)
The split
method creates an Array[String]
, so access the array elements and cast them to the desired types to create your variables:
val name = tokens(0) val age = tokens(1).toInt val weight = tokens(2).toDouble
Note that the age and weight fields in this example can throw a NumberFormatException
.
A fourth way to read the user’s input is by specifying a regular expression to match the input you expect to receive. Using this technique, you again receive each variable as a String
, and then cast it to the desired type. The process looks like this in the REPL:
scala> val ExpectedPattern = "(.*) (\\d+) (\\d*\\.?\\d*)".r ExpectedPattern: scala.util.matching.Regex = (.*) (\d+) (\d*\.?\d*) // you would use readLine() here scala> val input = "Paul 36 180.0" input: String = Paul 36 180.0 scala> val ExpectedPattern(a, b, c) = input a: String = Paul b: String = 36 c: String = 180.0
Now that you have the variables as strings, cast them to the desired types, as before:
val name = a val age = b.toInt val weight = c.toDouble
The ExpectedPattern
line in this example will fail with a scala.MatchError
if the input doesn’t match what’s expected.
Hopefully with all of these examples you’ll find your own preferred way to read in multiple values at one time.
Fun with output
Use print
, printf
, or println
to write output. As shown in the Solution, the readLine
method also lets you prompt a user for input.
The Console
object contains a number of fields that you can use with the print methods to control the display. For instance, if you want your entire line of output to be underlined, change the last lines of the script to look like this:
val qty = 2 val pizzaType = "Cheese" val total = 20.10 print(Console.UNDERLINED) println(f"$qty%d $pizzaType pizzas coming up, $$$total%.2f.") print(Console.RESET)
This prints the following string, underlined:
2 Cheese pizzas coming up, $20.10.
Other displayable attributes include colors and attributes such as BLINK
, BOLD
, INVISIBLE
, RESET
, REVERSED
, and UNDERLINED
. See the Console
object Scaladoc page for more options.
See Also
- Recipe 1.8, “Extracting Parts of a String That Match Patterns” for more examples of the pattern-matching technique shown in this recipe.
- The Java Scanner class
- The Java Pattern class
- The Scala Console object provides the
read*
methods
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |