我需要为不熟悉scala(既不是Java)但熟悉Shell的用户开发API。他们基本上会在scala类中编写shell脚本(我知道我可以调用外部shell脚本,但是来吧!此外,稍后我们将为常见的shell任务提供一些函数)。
我希望能够完成类似的事情:
1 object MyCoolScript extends MyMagicTrait {
2 $ "mkdir /tmp/test"
3 $ "cd /tmp/test"
4 $ "wget some-url"
5 }
更直接,如何将第2-4行(或可能不那么简洁的版本)转换为我可以在MyMagicTrait中处理的Seq [String]?
我知道 sys.process.stringToProcess ,但如果我有:
object MyCoolScript extends MyMagicTrait {
"mkdir /tmp/test" !!
"cd /tmp/test" !!
"wget some-url" !!
}
如何以简洁的方式获得每个命令的结果?另外,我希望有一个$“xxx”符号。
发布回答更新:
感谢@debilski,@ atensi和@ daniel-c-sobral,我能够非常接近所需的实现:https://gist.github.com/2777994
答案 0 :(得分:5)
class Shell {
var elems = Vector[String]()
def >(str: String) = elems :+= str
def run() = elems.map( whatever )
}
val shell = new Shell
shell> "mkdir /tmp/test.dir"
shell> "cd /tmp/test.dir"
答案 1 :(得分:4)
看起来Scala 2.10附带的字符串插值可以帮到你。首先,您可以实现简单的$
方法,它只是立即执行命令。为了实现这一点,您需要在StringContext
上添加此自定义方法:
object ShellSupport {
implicit class ShellStrings(sc: StringContext) {
def $(args: Any*) =
sc.s(args: _*) split "\n" map (_.trim) filterNot (_.isEmpty) foreach { cmd =>
// your excution logic goes here
println(s"Executing: $cmd")
}
}
}
现在您可以像这样使用它:
import ShellSupport._
val testDir = "/tmp/test"
$"mkdir $testDir"
$"cd $testDir"
$"""
wget some-url
wget another-url
"""
你可以利用它的语法(唯一的缺点,就是你不能在$
和"
之间添加空格)和命令中的字符串插值。
现在让我们尝试实现你的魔法特质。它通常是相同的想法,但我也使用DelayedInit
来正确定义命令,然后在创建类时自动执行它们。
trait MyMagicTrait extends DelayedInit {
private var cmds: List[String] = Nil
def commands = cmds
implicit class ShellStrings(sc: StringContext) {
def $(args: Any*) = {
val newCmds = sc.s(args: _*) split "\n" map (_.trim) filterNot (_.isEmpty)
cmds = cmds ++ newCmds
}
}
def delayedInit(x: => Unit) {
// your excution logic goes here
x
cmds map ("Excutintg: " + _) foreach println
}
}
它的用法:
class MyCoolScript extends MyMagicTrait {
val downloader = "wget"
$"mkdir /tmp/test"
$"cd /tmp/test"
$"""
$downloader some-url
$downloader another-url
"""
}
new MyCoolScript
这两种解决方案都产生相同的输出:
Executing: mkdir /tmp/test
Executing: cd /tmp/test
Executing: wget some-url
Executing: wget another-url
答案 2 :(得分:3)
这只是在@ Debilski和@ Tomasz的想法上表现出来,以表明你可以很好地结合它们:
trait Magic {
object shell {
var lines = IndexedSeq[String]()
def >(xs: String) { lines ++= xs.split("\n").map(_.trim) }
}
def doSomethingWithCommands { shell.lines foreach println }
}
object MyCoolScript extends App with Magic {
println("this is Scala")
shell> """
mkdir /tmp/test
cd /tmp/test
"""
doSomethingWithCommands
shell> """
wget some-url
"""
}
如果你想将shell与Scala命令结合起来,我真的不知道你怎么能得到更少的样板,因为你需要一些东西来展示shell的开始和结束。
答案 3 :(得分:2)
class Shell(commands: String) {
commands.lines foreach {
command =>
//run your command
}
}
class MyCoolScript extends Shell("""
mkdir /tmp/test
cd /tmp/test
wget some-url
""")
答案 4 :(得分:2)
这些结果到处都是。唉,我不认为sys.process就足够了。让我们首先写出这个例子,以这样的方式完成你所要求的:
val result = (
"mkdir /tmp/test ###
"cd /tmp/test" ###
"wget someurl
).lines_!
现在,为什么它不起作用:
首先,cd
将抛出异常 - 没有cd
可执行文件。这是一个内部shell命令,所以只有shell才能执行它。
接下来,假设cd
可行,wget
将不发生在/tmp/test
。上述每个命令都在Java的当前工作目录中重新执行。您可以指定执行每个命令的目录,但是您不能拥有CWD(它不是“可变的”)。
答案 5 :(得分:1)
修改强>
我会这样做:
res = List("mkdir /tmp/test", "cd /tmp/test", "wget some-url").map(_!!)
// res is a List[String] of the output of your commands
您可以像这样执行系统命令:
scala> import scala.sys.process._
import scala.sys.process._
scala> "pwd"!
/Users/kgann
res0: Int = 0
你应该可以将它包装成DSL,但它已经非常简洁了。