(作者:玛瑙河。尊重他人劳动成果,转载请注明作者或出处)
在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:
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:
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这个文件中我们找到如下的代码片段:
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指令的文件头就被去除了,脚本剩下的部分就能被编译执行了(当然编译过程中还有其它的魔术,这个以后再讲)。
(作者:玛瑙河。尊重他人劳动成果,转载请注明作者或出处)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· .NET 进程 stackoverflow异常后,还可以接收 TCP 连接请求吗?
· SQL Server统计信息更新会被阻塞或引起会话阻塞吗?
· C# 深度学习框架 TorchSharp 原生训练模型和图像识别
· 这或许是全网最全的 DeepSeek 使用指南,95% 的人都不知道的使用技巧(建议收藏)
· 拒绝繁忙!免费使用 deepseek-r1:671B 参数满血模型
· 本地搭建DeepSeek和知识库 Dify做智能体Agent(推荐)
· Sdcb Chats 重磅更新:深度集成 DeepSeek-R1,思维链让 AI 更透明!
· DeepSeek-R1本地部署如何选择适合你的版本?看这里