Прямо скажем, использование Scala в качестве языка для написания скриптов — довольно сомнительная идея. Язык действительно можно использовать таким образом. Но проблема заключается в том, что скрипты довольно долго стартуют. На моей машине время запуска одного скрипта на Scala составляет около 4-5 секунд. На всяких же там ультрабуках это время еще больше.

В результате убивается, пожалуй, одно из самых главных преимуществ скриптовых языков — скорость разработки. Не говоря уже о том, что не всякий пользователь станет устанавливать JVM и тем более Scala ради запуска скриптов, а затем ждать 5 и более секунд при каждом запуске. Уж проще скомпилировать небольшую программку и распространять ее в виде fat jar. Тем не менее, в некоторых случаях такой сценарий использования Scala может быть оправдан. Например, если вся команда пишет только на Scala, и следовательно на этом языке написан какой-то код, который хочется использовать в скриптах.

Простейший скрипт на Scala выглядит так:

#! /usr/bin/sbt -Dsbt. version = 0.13.7 -Dsbt. main . class = sbt. ScriptMain
!#

/***
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. Помимо нее также можно указывать необходимые зависимости:

#! /usr/bin/sbt -Dsbt. version = 0.13.7 -Dsbt. main . class = sbt. ScriptMain
!#

/***
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. Например, так можно получить код возврата:

#! /usr/bin/sbt -Dsbt. version = 0.13.7 -Dsbt. main . class = sbt. ScriptMain
!#

/***
scalaVersion := «2.11.7»
*/

import sys. process . _

val code = getExitCode ( «pwd» )
println ( s «code = $code» )

def getExitCode ( cmd : ProcessBuilder ) : Int = {
cmd. ! ( ProcessLogger ( line => ( ) ) )
}

Для получения текста, выведенного процессом, я написал такую функцию:

def run ( cmd : ProcessBuilder ) : String = {
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:

def runVerbose ( cmd : ProcessBuilder ) : String = {
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
}

Вывод команд можно перенаправлять при помощи оператора #| :

def getInstanceStatus ( instanceId : String ) : String = {
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 . Мне лично не очень нравится использование неявных преобразований для таких задач, но интерфейс придумывал не я.

Довольно часто может потребоваться перенаправить вывод команды в файл:

#! /usr/bin/sbt -Dsbt. version = 0.13.7 -Dsbt. main . class = sbt. ScriptMain
!#

/***
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._ есть и другие интересные операторы. По моему опыту они редко нужны на практике, тем не менее знать о них полезно:

scala> import 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

EnglishRussianUkrainian