本文介绍了我如何尝试使用 Go 语言进行脚本编程的经历。文中我将讨论 Go 脚本的必要性,我们预期的表现以及可能的实现方式。在讨论过程中,我讲深入探讨脚本、Shell 和 Shebang。最终,我们将会讨论让 Go 脚本工作的解决方案。

  为什么 Go 语言适合编写脚本?

  通常认为,Python 和 Bash 是热门的脚本语言,而 C、C++ 和 Java 完全不能被用作脚本编程,有一些语言却夹在其中。

  Go 语言试用场景很多,从编写 Web 服务器到流程管理,甚至有些人用作系统编程语言。在后文中,我将论证,除了上述这些场景外,Go 语言还可以简单地用于编写脚本。

  是什么让 Go 语言适合编写脚本?

  Go 语言简洁易读,并且不太冗长。这使得编写的脚本易于维护且相对较短。Go 语言有许多可用于各种用途的库。假设这些库是稳定且经过测试的,这可以让脚本简洁且健壮。如果我的大多数代码使用 Go 语言编写,那么我更倾向于使用 Go 作为我的脚本语言。当代码由许多人协作维护,那么使用一种大家都能完全掌控的语言会降低维护成本,即使是一些脚本。Go 语言已经 99% 支持脚本

  事实上,我已经可以使用 Go 语言来编写脚本。这需要使用 Go 的 run 子命令:如果脚本名称是 my-script.go,我们可以简单的通过 go run my-script.go 来运行。

  这里,对于 go run 命令,我认为需要特别关注一下。让我们详细说明下。

  Go 语言区别于 Bash 和 Python 的地方是后者通过解释执行,既它们的脚本在读取的时候执行。而对于 Go 语言,当用户输入了 go run,Go 编译这个 Go 程序,然后再执行。因为 Go 编译时间非常短,所以看上去像是解释执行。值得提醒的是,很多人都说“go run 只是一个玩具”,但是如果我们需要脚本,同时也喜欢 Go 语言,那么这个玩具就是我们想要的。

  所以已经支持的很好了,对吧?

  我们可以编写脚本,并通过 go run 命令来执行。还有什么问题呢?问题是我很懒,希望通过类似https://baijiahao.baidu.com/my-script.go 的方式来运行脚本,而不是 go run my-script.go。

  这里我们讨论一个简单的脚本和 Shell 通过两种方式进行交互:它从命令行获取输入数据,并设置退出状态码。二者并非所有可能的交互方式(除此之外还可以有环境变量、信号、标准输入、标准输出、标准错误等),但是 Shell 脚本中较困难的两个。

  这个脚本输出“Hello”和从命令行获取的第一个参数,并设置退出状态码为 42:

  package mainimport ( "fmt" "os")func main() { fmt.Println("Hello", os.Args[1]) os.Exit(42)}这时,使用 go run 命令结果有些奇怪:

  $ go run example.go worldHello worldexit status 42$ echo $?1这个问题我们稍后会讨论。

  这时候可以使用 go build 命令。这是通过 go build 命令执行该脚本的方式:

  $ go build$ https://baijiahao.baidu.com/example worldHello world$ echo $?42此时调试该脚本的流程变成了:

  $ vim https://baijiahao.baidu.com/example.go$ go build$ https://baijiahao.baidu.com/example.go worldHi world$ vim https://baijiahao.baidu.com/example.go$ go build$ https://baijiahao.baidu.com/example.go worldBye world而我期望达到的是这样来运行脚本:

  $ chmod +x example.go$ https://baijiahao.baidu.com/example.go worldHello world$ echo $?42而对应的工作流程是:

  $ vim https://baijiahao.baidu.com/example.go$ https://baijiahao.baidu.com/example.go worldHi world$ vim https://baijiahao.baidu.com/example.go$ https://baijiahao.baidu.com/example.go worldBye world看上去很简答是吧?

  Shebang

  类 UNIX 系统支持Shebang。Shebang 用于告诉 Shell 使用什么解释器来运行脚本。我们可以根据编写脚本使用的语言来设置 Shebang 行。

  通常来说,我们会使用env命令最为脚本执行器,这样就无需再使用解释器的绝对路径。例如:可以设置 Shebang 为 #! /usr/bin/env python 让 Python 解释器来运行该脚本。当名称为 example.py 的脚本有上述的 Shebang 行,同时它具有可执行属性(可以通过 chmod +x example.py 命令添加)时,可以在 Shell 中输入https://baijiahao.baidu.com/example.py arg1 arg2 来运行。此时 Shell 会读取 Shebang 行,然后开始链式反应:

  Shell 开始运行 /usr/bin/env python example.py arg1 arg2。这实际就是 Shebang 行加上脚本名再加上额外的参数。该命令执行 /usr/bin/env,参数是 /usr/bin/env python example.py arg1 arg2。然后 env 命令调用 python 命令,执行 python example.py arg1 arg2。最后 python 运行 example.py 脚本,参数是 example.py arg1 arg2。

  让我们开始尝试给 Go 脚本添加 Shebang。

  1、 第一次幼稚的尝试

  我们首先设置一个幼稚的 Shebang 来使用 go run 执行这个脚本。加了 Shebang 之后的脚本看上去是这样的:

  #! /usr/bin/env go runpackage mainimport ( "fmt" "os")func main() { fmt.Println("Hello", os.Args[1]) os.Exit(42)}然后尝试运行一下,输出为:

  $ https://baijiahao.baidu.com/example.go/usr/bin/env: ‘go run’: No such file or directory发生了什么?

  Shebang 机制将 go run 整体作为 env 命令的一个参数了,而实际不存在这个命令。输入 which "go run" 也会有类似的错误。

  2 、第二次尝试

  一个可行的方案是将 Shebang 设置为 #! /usr/local/go/bin/go run。在我们尝试之前,就可以会发现一个问题:go 二进制文件在不同系统路径不同,写死绝对路径会导致脚本无法兼容安装在其他位置的 go。另外一个解决方案是使用 alias gorun="go run" 来创建一个别名,之后就能把 Shebang 修改成 #! /usr/bin/env gorun。使用这种方式,我们需要在运行这个脚本的系统中都设置这个别名。

  输出:

  $ https://baijiahao.baidu.com/example.gopackage main:example.go:1:1: illegal character U+0023 '#'解释:

  从这个输出来看,我们有一个好消息,同时也有一个坏消息,你想先听哪个?我先来说好消息:-)

  好消息是这个方案成功了,执行脚本之后 go run 命令正常调用了。坏消息:井号。在许多脚本语言中,Shebang 开头的井号会被当成注释忽略。但是对 Go 语言编译器来说,开头的井号变成了“非法字符”。3、 解决方案

  当脚本不包含 Shebang 行时,不同的 Shell 会回退到不同的解析器。Bash 会使用自己来运行脚本,而 zsh 会回退到使用 sh。这给我们提供了一种解决方案,这也是StackOverflow上提到的一种解决方案。

  由于 // 是 Go 语言中定义的注释,而我们可以使用 //usr/bin/env 来替代 /usr/bin/env(在路径分割符中,//==/),因此第一行可以设置成:

  //usr/bin/env go run "$0" "$@"结果:

  $ https://baijiahao.baidu.com/example.go worldHi worldexit status 42https://baijiahao.baidu.com/test.go: line 2: package: command not foundhttps://baijiahao.baidu.com/test.go: line 4: syntax error near unexpected token `newline'https://baijiahao.baidu.com/test.go: line 4: `import ('$ echo $?2解释:

  我们距离成功又近了一步:终于有了正确的输出。但是输出中还包含一些错误,同时状态码也不对。让我们来看下到底发生了什么。正如之前所说的,Bash 没有找到任何 Shebang,因此选择使用 bash https://baijiahao.baidu.com/example.go world 的方式来运行脚本(直接使用该命令会有相同输出,你也可以试下)。非常有意思,直接使用 Bash 来运行 Go 文件 :-) 下一步,Bash 读取脚本的第一行,然后运行该命令:/usr/bin/env go run https://baijiahao.baidu.com/example.go world。之前脚本中的“0”代表第一个参数,因此实际值是我们运行的脚本文件名。“

  0”代表第一个参数,因此实际值是我们运行的脚本文件名。“@”表示命令行中的所有参数。在这个例子中会被替换成“world”。到目前位置,使用https://baijiahao.baidu.com/example.go world,脚本使用了正确的命令行参数,并输出了正确的值。

  输出中还有诡异的一行:“exit status 42”。这是什么?如果我们自己尝试下命令就会了解:

  $ go run https://baijiahao.baidu.com/example.go worldHello worldexit status 42$ echo $?1这是 go run 命令通过标准错误输出的。go run 命令屏蔽了状态码,然后返回了状态码 1。关于这个行为的讨论,可以参见Github issue。

  好了,那么其他几行输出呢?这是 Bash 试图解析 Go 源码,但实际失败了。

  4 、解决方案优化

  这个 StackOverflow 页面建议在 Shebang 之后加上 ;exit “$?”。这会告诉 Bash 解释器不要再继续执行。

  完整的 Shebang:

  //usr/bin/env go run "$0" "$@"; exit "$?"结果:

  $ https://baijiahao.baidu.com/test.go worldHi worldexit status 42$ echo $?1基本上实现了:这里实现了让 Bash 使用 go run 命令执行脚本,然后立即退出,同时设置状态码为 go run 命令执行后的状态码。

  更进一步,可以在 Shebang 行中添加一些命令,用于移除标准错误中的“退出状态”内容,甚至解析该文本并作为整个脚本的返回码。

  然而:

  再增加 Bash 命令意味着冗长的 Shebang 行,这与最初期望的 #! /usr/bin/env go 相比过于复杂。记住这只是一种 hack 的方式,而我并不喜欢 hack。毕竟我们只是想用标准的 Shebang 机制。为什么?因为这样简单、标准、优雅。这或多或少也是我想找一种更加方便的语言作为脚本语言(例如 Go)来替代 Bash 的原因。幸运的是,我们有gorun

  gorun 就是我们想要的。我们只需在 Shebang 中写 #! /usr/bin/env gorun,并赋予脚本可执行权限。仅此而已,我们可以在 Shell 中执行,获得期望的结果!

  $ https://baijiahao.baidu.com/example.go worldHello world$ echo $?42太棒了!

  警告:兼容性

  当文件包含 Shebang 之后,Go 将无法编译(和我们之前看见的一样)。

  $ go run example.gopackage main:example.go:1:1: illegal character U+0023 '#'这两种选择不能兼得,我们只能二选一:

  使用 Shebang,并通过https://baijiahao.baidu.com/example.go 方式运行脚本。或者移除 Shebang,使用 go run https://baijiahao.baidu.com/example.go 运行脚本。二者不可兼得!

  另外一个问题,是当脚本文件被放在 Go 工程中时,编译器会发现这个 go 文件。虽然该文件并不是应用程序所需要的,也会导致编译失败。一个解决方案是移除.go 后缀,但是这样就会无法使用类似 go fmt 等工具。

  最后一些想法

  本文讨论了使用 Go 语言来编写脚本的重要性,同时介绍了几种方式来实现脚本运行。这里有一些总结。

  类型退出状态码可执行可编译标准go rungorun// 解决方案

  解释:

  类型:如何运行脚本。退出状态码:脚本执行后,是否设置了脚本的退出状态码。可执行:脚本是否可以通过 chmod +x 设置可执行权限。可编译:脚本是否可以通过 go build。标准:脚本是否需要标准库之外的东西。正如上表,目前没有一种完美的解决方案。看上去最方便且问题最少的方式是使用 go run 命令。但是在我看来,这种方式太过“复杂”,而且无法“可执行”,同时退出状态码也不正确。这将会导致难以区分脚本是否正确执行。

  因此,我认为 Go 语言在这个领域仍然有许多工作要做。我不认为让语言支持忽略 Shebang 行会有什么问题。这将会解决执行问题,但是类似这种变化可能不会被 Go 社区采纳。

  我的同事提醒我事实上 Shebang 行对于 Javascript 同样也是非法的。但是在 Node.js 中,他们增加了一个跳过 Shebang函数,让 Node 脚本可以在 Shell 中直接运行。(译者注:由于原文时间比较久远,在c2b01881dcb3bf302f9d83157e719cc5240a9042版本之后 Node.js 已经对源码进行了重构,在702331be906fe58e0ef66c7b31c7d2aeb3af3421版本之后,原文提及的 stripShebang 函数已经被移除。)

  如果 gorun 可以作为标准工具的一部分就更棒了,其他类似的还有 gofmt 和 godoc。

  原文链接:

  https://posener.github.io/go-shebang-story/

  举报/反馈

一次使用 Go 语言编写脚本的经历相关推荐

  1. c 语言编写脚本优化,两周自制脚本语言-第11天 优化变量读写性能

    第11天 优化变量读写性能 以变量值的读写为例,向读者介绍基于这种理念的语言处理器性能优化方式. 11.1 通过简单数组来实现环境 假如函数包含局部变量x与y,程序可以事先将x设为数组的第0个元素,将 ...

  2. 编写脚本电脑怎么编写界面_在任何无法理解的情况下,请编写脚本

    编写脚本电脑怎么编写界面 脚本编写是使您的应用程序在运行时就可根据客户需求进行调整的最流行的方法之一. 与往常一样,此方法不仅带来好处,例如,在灵活性和可管理性之间存在众所周知的折衷方案. 本文不是从 ...

  3. 在任何无法理解的情况下,请编写脚本

    脚本编写是使您的应用程序在运行时就可以根据客户需求进行调整的最流行的方法之一. 与往常一样,此方法不仅带来好处,例如,在灵活性和可管理性之间存在众所周知的折衷方案. 本文不是从理论上讨论优缺点的文章之 ...

  4. python语言+selenium自动化,编写脚本调用Chrome、Firefox浏览器打开百度网站

    python语言+selenium自动化,编写脚本调用Chrome.Firefox浏览器打开百度网站 目标:初始化一个webdriver实例对象driver,通过webdriver.Chrome()和 ...

  5. fceux源码解析_FCEUX金手指加强版 - 使用Lua脚本语言编写FC/NES金手指脚本

    一直觉得大部分的FC/NES模拟器的作弊码金手指不是那么方便使用, 比如魂斗罗1代, 玩家的武器可以通过修改0xAA的值来改变: 0x11为M弹(重机枪),0x12为F弹(圈圈),0x13为S弹(散弹 ...

  6. 用go语言编写办公脚本——根据txt内容搜索对应文件

    用go语言编写办公脚本--根据txt内容搜索对应文件 最近工作中,需要处理大量的漏扫报告,需要根据不同的标准来将混在一起的报告分开,公司有个大佬直接写了.bat的批处理脚本,感叹一句,真强啊!!但是这 ...

  7. matlab编写正整数阶乘函数,用matlab语言编写程序:编写一个计算阶乘的函数,再编写一个脚本文件,通过键盘输入计算阶乘的n值....

    点击查看用matlab语言编写程序:编写一个计算阶乘的函数,再编写一个脚本文件,通过键盘输入计算阶乘的n值.具体信息 答:编写一个matlab文件,求1到n的阶乘之和.其代码编写的目的,就是学会自定义 ...

  8. QTP 脚本语言编写入门到精通(一)

    飞机订票登陆系统flight 一.编写用户登录测试用例. 二.直接编写脚本 '****************** 'SystemUtil.Run PathFinder.Locate(".. ...

  9. 实用c语言函数源码,C语言编写简单朗读小工具(有源码)

    原标题:C语言编写简单朗读小工具(有源码) 最近不少人在后台留言说学C都是面对枯燥的控制台程序,能不能体现一下C语言的实际用途,今天我们就理论结合实践一把:C语言结合VBS脚本编写一个简单的朗读小工具 ...

最新文章

  1. 小程序获取图片的宽高
  2. 节后招人平均工资9000上热搜,为什么有些人去哪里都值钱?
  3. tensorflow 入门
  4. Tizen 2.0 SDK 和源码发布
  5. 推荐一个博客,或许给技术流的自己一些启示
  6. 遥感、地理空间数据、全国基础数据下载网站大全汇总
  7. 获取网络图片并异步更新UI
  8. BFC和haslayout(IE6-7)(待总结。。。)
  9. [转]Linux下的动态连接库及其实现机制
  10. GEE-Scholars 数据工具--Sentinel-2遥感植被指数库
  11. 2021-10-26
  12. 51ditu、清华地图以及Google地图
  13. 8脚 tja1050t_TJA1050T设计的CAN总线通信硬件电路原理图解
  14. Mac系统安装软件的三种方式
  15. Android安全相关
  16. Android EventBus的实现原理
  17. android adb修复工具,Android——adb修复build.prop
  18. 彻底解决MySQL导入Excel无法打开Excel的问题
  19. USB-CDC-ECM 类设备之USB2.0接口100M以太网芯片 SR9900(A)
  20. oracle bpm难点,Oracle Bpm 11g 审批性能优化

热门文章

  1. 图像倾斜校正 Radon 变换原理及函数
  2. ajax读取json文件 / 跨域问题
  3. 美8家最具潜力新公司:在线旅游和新媒体居多
  4. powershell导入脚本失败,禁止运行脚本,无法远程连接服务器
  5. docker(七)容器与外部通信
  6. android webview崩溃,Android-未知的webview崩溃原因
  7. IOS-内存泄漏检测工具Instruments中的Leaks
  8. matlab站点插值格点,基于xarray的气象场站点和格点插值
  9. 优品优男所谓“日有所思,夜有所梦”
  10. 读论文《对 感知音频质量 的 客观评估 综述---对其 应用领域依赖性 的评估》