惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Cloudflare Blog
U
Unit 42
F
Fortinet All Blogs
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
Y
Y Combinator Blog
罗磊的独立博客
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
B
Blog RSS Feed
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
I
InfoQ
博客园 - 叶小钗
博客园 - 聂微东
Last Week in AI
Last Week in AI

博客园 - 玛瑙河

用matplotlib绘制带误差的条形图及中英文字体设置 Python中用MacFSEvents模块监视MacOS文件系统改变一例 修改gnome-shell扩展“Applications Menu”的菜单区域宽度。 VirtualBox 4.3.18 启动虚拟机时显示不能加载 R3模块并退出故障解决一例 自定义IPython Qt Console 窗口大小、字体、颜色 Windows Server 2012 R2 两个奇葩问题的解决 cherokee +php fastcgi 出现 No input file specified 故障一例 ubuntu下从源码编译安装cherokee liftweb整合ckfinder进行文件上传与管理 命令行下多线程下载工具 Axel 2.4 for Windows 正确设置H2数据库的Collation解决中文排序问题 解决MASM编程对话框中文问题 清除U盘子目录变成1K大小的快捷方式病毒的脚本 Google URL Shorter HTML中常用的特殊字符实体编码(Character entity references in HTML 4) - 玛瑙河 MrBayes v3.2 for windows 并行版下载及 checkpoint 功能介绍 强制垃圾回收解决.NET Office互操作中文件锁未能释放的问题 童言无忌之小瑈与甲虫 MrBayes v3.1.2 for windows 并行版下载
Scala 脚本的 pound bang 魔术
玛瑙河 · 2010-09-07 · via 博客园 - 玛瑙河

 (作者:玛瑙河。尊重他人劳动成果,转载请注明作者或出处)

在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指令的文件头就被去除了,脚本剩下的部分就能被编译执行了(当然编译过程中还有其它的魔术,这个以后再讲)。

 (作者:玛瑙河。尊重他人劳动成果,转载请注明作者或出处)