Scala 脚本的 pound bang 魔术

在scala语言的创始者Martin Odersky等人所著的《Programing in Scala -- A comprehensive step-by-step guide》一书的附录A中,描述了在Unix和Windows执行Scala脚本的方法:

Appendix A
Scala scripts on Unix and Windows

If you’re on some flavor of Unix, you can run a Scala script as a shell script
by prepending a “pound bang” directive at the top of the file. For example,
type the following into a file named helloarg:

1  # !/ bin / sh
2  exec scala  " $0 "   " $@ "
3    ! #
4    //  Say hello to the first argument
5    println( " Hello,  " +  args( 0 + " ! " )

 

The initial #!/bin/sh must be the very first line in the file. Once you set its
execute permission:
$ chmod +x helloarg
You can run the Scala script as a shell script by simply saying:
$ ./helloarg globe

 
If you’re on Windows, you can achieve the same effect by naming the
file helloarg.bat and placing this at the top of your script:

1  ::# !
2  @echo off
3  call scala  % 0   %*
4  goto  :eof
5  :: ! #
6 

 

由此可以看到:在*nix环境中(大家应当想到shebang了吧), 可以通过 exec scala "$0" "$@" 指令来指定用 scala 脚本本身,而在window环境中,可以在批处理文件中调用call scala %0 %* 来执行脚本本身(这里  $0和 %0都代表脚本本身)。但是问题来了,当真正用scala执行这个脚本的时候,::!和::!#之间的内容是不符合scala语法的,这部分内容只能被shell执行,直接用scala 执行显然会出现编译错误。因此很自然的想到这其中肯定有某种魔术,在编译执行过程中肯定会以某种方式去除这段 shell指令。

这种魔术是如何实现的呢?搜索源码,在 scala-compiler-src\scala\tools\nsc\util\SourceFile.scala这个文件中我们找到如下的代码片段:

复制代码
 1  object ScriptSourceFile {
 2     /**  Length of the script header from the given content, if there is one.
 3     *  The header begins with "#!" or "::#!" and ends with a line starting
 4     *  with "!#" or "::!#".
 5      */
 6    def headerLength(cs: Array[Char]): Int  =  {
 7      val headerPattern  =  Pattern.compile( """ ^(::)?!#.*(\r|\n|\r\n) """ , Pattern.MULTILINE)
 8      val headerStarts   =  List( " #! " " ::#! " )
 9      
10       if  (headerStarts exists (cs startsWith _)) {
11        val matcher  =  headerPattern matcher cs.mkString
12         if  (matcher.find) matcher.end
13         else   throw   new  IOException( " script file does not close its header with !# or ::!# " )
14      }
15       else   0
16    }
17    def stripHeader(cs: Array[Char]): Array[Char]  =  cs drop headerLength(cs)
18    
19    def apply(file: AbstractFile, content: Array[Char])  =  {
20      val underlying  =   new  BatchSourceFile(file, content)
21      val headerLen  =  headerLength(content)
22      val stripped  =   new  ScriptSourceFile(underlying, content drop headerLen, headerLen)
23      
24      stripped
25    }
26  }
复制代码
以上声明了一个ScriptSourceFile对象,其apply方法中的content drop headerLen(第22行)是用来去除作为shell指令的文件头的。apply是一个Factory方法,当我们用ScriptSourceFile.apply来创建一个ScriptSourceFile实例的时候,作为shell指令的文件头就被去除了,脚本剩下的部分就能被编译执行了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值