8

我需要为不熟悉 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、@tenshi 和@daniel-c-sobral,我能够提出非常接近所需的实现:https ://gist.github.com/2777994

4

6 回答 6

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"
于 2012-05-22T19:32:37.040 回答
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
 """ 

您可以利用它的语法(它唯一的缺点是不能在$and之间添加空格")和命令中的字符串插值。


现在让我们尝试实现你的魔法特质。这通常是相同的想法,但我也使用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
于 2012-05-22T20:50:31.370 回答
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 的开始和结束位置。

于 2012-05-22T20:02:40.007 回答
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

""")
于 2012-05-22T19:05:51.803 回答
2

这些结果到处都是。唉,我认为 sys.process 不足以满足您的需求。让我们首先写出该示例,使其符合您的要求:

val result = (
    "mkdir /tmp/test ###
    "cd /tmp/test" ###
    "wget someurl
).lines_!

现在,为什么它不起作用:

首先,cd会抛出一个异常——没有cd可执行文件。这是一个内部 shell 命令,所以只有 shell 可以执行它。

接下来,假设cd会起作用,则wget不会发生/tmp/test. 上面的每个命令都在 Java 的当前工作目录中重新执行。您可以指定要执行每个命令的目录,但不能有 CWD(它不是“可变的”)。

于 2012-05-22T22:46:42.250 回答
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 中,但它已经非常简洁了。

于 2012-05-22T19:09:19.133 回答