Прямо скажем, использование 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