您的位置:首页 > 运维架构 > Shell

Clojure:在REPL上实现一个简单的shell(二)

2012-05-26 01:29 190 查看
前文再续,书接上一回……

话说上次我们已经实现了一个简单的骨架,它包括一个挂在REPL上面的钩子函数(ask函数)和入口函数(shell函数)。关于ask和shell,具体请参考这里

首先为了显得更正式点,我们对shell函数作了一点小的修改:

(defn shell[& p]

  (let [prompt (if (empty? p)

                 "myShell"

                 (first p))

        exit-cmd #{"8" "88" "quit" "q" "Q" "bye"}]

    (loop [cmd (ask prompt)]

      (if (exit-cmd cmd)

        (println "get out!")

        (do 

          (exec cmd)

          (recur (ask prompt)))))))

跟上一篇的代码对比,我们主要修改/增加了退出命令,现在只要在我们的shell中输入("8" "88" "quit" "q" "Q" "bye")中的任意一个都会退出shell(*也许有人注意到我们没包括“exit”,因为“exit”在很多系统里面都是系统命令)。

今天我们试着实现执行函数“exec”,exec的签名是“(exec cmd)”。exec函数所接受的输入都是字符串,这种输入的字符串有几种可能性,下面列出几种可能性以及我们希望看到的执行结果:

字符串代表了系统命令 - 执行系统命令并返回执行结果
字符串代表了一个完整的合法的Clojure形式(Clojure Form) - 进行Clojure求值并返回求值结果
字符串代表了一个完整的Clojure形式 - shell将像REPL一样继续请求输入直到完成一个完整的Clojure形式
既不是系统命令也不是合法的Clojure形式 - 报错

因此简单地,下面是它的代码:



在解读上面的代码之前,先简单说明一下两个关键的使用到的Clojure函数:

read-string:读入一个字符串并解释,如果这个字符串是合法的Clojure形式则返回解释好的对象,否则报错。返回的对象可由求值函数(eval)进行求值;
clojure.java.shell/sh:clojure.java.shell包里的sh函数根据(以字符串形式)传入的指令和指令参数,通过调用Runtime.exec()创建子进程来执行外部命令。
关于这两个函数更具体的说明请参考相关文档。为了不那么啰嗦,下面描述按照有效代码一行行解释:

第13行:使用read-string来尝试获得Clojure对象。但是如果函数判断读入的是对象未完成输入就会抛出“java.lang.RuntimeException: java.lang.Exception: EOF while reading”异常,这就是为什么我们将代码放进try/catch块里面
第14行,对返回的对象进行求值。如果求值成功则说明用户输入是合法的Clojure形式,我们在求值成功后打印求值结果
第15行,捕捉异常。如果在之前两步里抛出了异常,有两种情况:
用户输入Clojure形式但是不完整,这个异常的报错消息是“EOF while reading
用户输入完整,这又分两种情况:
用户输入的是一个外部命令调用
用户输入的是合法、完整的Clojure形式,但是返回对象在当前空间不存在(*通常是read-string返回一个符号,但是该符号在当前空间没有对应的对象因此会导致求值失败)

第16~19行,一旦遇到“EOF while reading”异常,我们会要求用户继续输入,并将输入拼接起来作为新的输入调用exec
第21~24行,否则我们先假设输入是外部命令调用,于是使用sh函数进行调用,并打印调用结果
第25~最后行,如果外部调用抛出异常,则打印这个异常,并且额外地打印之前求值(eval)所抛出的异常
这里我们狡猾地利用了异常捕获机制,并先假设用户输入是Clojure代码、不是的话再假设输入是外部命令调用,如果两个假设都不济的话就利用捕获到的异常来做进一步的处理。

加载以上代码之后,现在只要在REPL中输入“(shell)”,就可以使用我们这个简单的shell了。经过一些简单的测试,发现它还是暂时符合我们“简单shell”的要求的,但是由于没时间做充分测试,因此bug是在所难免的吧,如有同学发现的话麻烦请告知。

同时,这个shell主要有以下几个不足的地方:

受限于Java对外部程序调用的限制,如果外部命令是交互式,则不能成功实现交互,这使得这个shell的用处大打折扣
sh函数的使用方式是“Clojure化”的,和平时使用shell命令的方式不一样,例如在shell中的“ls -al”,使用sh调用必须是(sh "ls" "-al"),因此我们的代码必须做进一步改进
因为sh在运行完毕后才返回结果,因此对于外部运行时间比较长的调用,用户不能实时观察到运行状态,这也使得这个shell的用处大打折扣
平常的系统中shell(这里指真正的shell如bash或者cmd)有内建命令,我们的程序不能“直接”使用内建命令,例如平时我们使用“dir”就直接敲“dir”就行了,但是使用sh的话就得用“(clojure.java.shell/sh "cmd" "/c" "dir")”,这个缺陷又使得我们的shell显得用处不大
综上所述,我们的shell基本上是没有什么实际用途的(至少到目前来说是这样的)。或许它最大的用途就是让我们学习和熟悉Clojure吧。如果有机会,也许后面还会继续深化改进这个shell吧(虽然看到它还能有什么实际用途 -_-|||)。。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息