Об использовании Scala в качестве скриптового языка
27 ноября 2015
Прямо скажем, использование Scala в качестве языка для написания скриптов — довольно сомнительная идея. Язык действительно можно использовать таким образом. Но проблема заключается в том, что скрипты довольно долго стартуют. На моей машине время запуска одного скрипта на Scala составляет около 4-5 секунд. На всяких же там ультрабуках это время еще больше.
В результате убивается, пожалуй, одно из самых главных преимуществ скриптовых языков — скорость разработки. Не говоря уже о том, что не всякий пользователь станет устанавливать JVM и тем более Scala ради запуска скриптов, а затем ждать 5 и более секунд при каждом запуске. Уж проще скомпилировать небольшую программку и распространять ее в виде fat jar. Тем не менее, в некоторых случаях такой сценарий использования Scala может быть оправдан. Например, если вся команда пишет только на Scala, и следовательно на этом языке написан какой-то код, который хочется использовать в скриптах.
Простейший скрипт на Scala выглядит так:
!#
/***
scalaVersion := "2.11.7"
*/
if(args.length < 1) {
println("Usage: ./hello.scala <name>")
System.exit(1)
}
val name = args(0)
println(s"Hello, $name!")
Делаем ему chmod u+x
, запускаем, радуемся. Вы, конечно же, заметили, что в скрипте можно указывать требуемую версию Scala. Помимо нее также можно указывать необходимые зависимости:
!#
/***
scalaVersion := "2.11.7"
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.2.11"
*/
import org.json4s._
import org.json4s.jackson.JsonMethods._
val t = parse("""{"postId":123, "text":"ololo"}""")
println(t)
При написании скриптов часто требуется взаимодействовать со сторонними программами. Тут на помощь приходит пакет sys.process. Например, так можно получить код возврата:
!#
/***
scalaVersion := "2.11.7"
*/
import sys.process._
val code = getExitCode("pwd")
println(s"code = $code")
def getExitCode(cmd: ProcessBuilder): Int = {
cmd.!(ProcessLogger(line => () ))
}
Для получения текста, выведенного процессом, я написал такую функцию:
val buffer = new StringBuffer()
val exitCode = cmd.!(ProcessLogger(line => buffer append s"$line\n"))
if(exitCode != 0) {
println(s"Command `$cmd` terminated with status $exitCode")
System.exit(1)
}
buffer.toString.trim
}
В некоторых случаях больше подходит функция runVerbose:
val buffer = new StringBuffer()
println("--------------------------------------------------------")
val exitCode = cmd.!(ProcessLogger({ line =>
println(line)
buffer append s"$line\n"
} : String => Unit) )
if(exitCode != 0) {
println(s"Command `$cmd` terminated with status $exitCode")
System.exit(1)
}
println("--------------------------------------------------------")
buffer.toString
}
Вывод команд можно перенаправлять при помощи оператора #|
:
run(
awsCmd(s"ec2 describe-instances --instance-ids $instanceId") #|
Seq("jq", "-r", ".Reservations[0].Instances[0].State.Name")
).trim
}
def awsCmd(cmd: String): String = {
s"aws --profile $profile --region $region $cmd"
}
К этому моменту вы, конечно же, успели обратить внимание, как String
и Seq[String]
неявно преобразуются в класс ProcessBuilder
, принимаемый функцией run
. Мне лично не очень нравится использование неявных преобразований для таких задач, но интерфейс придумывал не я.
Довольно часто может потребоваться перенаправить вывод команды в файл:
!#
/***
scalaVersion := "2.11.7"
*/
import sys.process._
import java.io._
run("ls -la" #| "wc -l" #> new File("/tmp/out.txt"))
def run(cmd: ProcessBuilder): String = {
// ... see above ...
}
В пакете sys.process._ есть и другие интересные операторы. По моему опыту они редко нужны на практике, тем не менее знать о них полезно:
import sys.process._
scala> import java.io._
import java.io._
scala> import scala.language.postfixOps
import scala.language.postfixOps
scala> "ls -la" #| "grep pdf" !
... some output here ...
res0: Int = 0
scala> "id" !!
res1: String =
"uid=1000(eax) gid=1000(eax) groups=...
"
scala> "id".!!.trim
res2: String = uid=1000(eax) gid=1000(eax) groups=...
scala> "wc -l" #< new File("/tmp/out.txt") !
1
res3: Int = 0
scala> "id" #>> new File("/tmp/out.txt") !
res4: Int = 0
scala> "id" #&& "pwd" !
uid=1000(eax) gid=1000(eax) groups=...
/home/eax/temp
res5: Int = 0
scala> "ls -la /no/such/file" #|| "true" !
ls: cannot access /no/such/file: No such file or directory
res6: Int = 0
Собственно, это все. А что вы думаете о написании скриптов на языке Scala?
Дополнение: Как я выбирал скриптовый язык и остановился на Python
Метки: Scala, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.