這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在 `Cloudflare` 的人們都非常喜歡 Go 語言。我們在許多[內部軟體項目](https://blog.cloudflare.com/what-weve-been-doing-with-go/)以及更大的[管道系統](https://blog.cloudflare.com/meet-gatebot-a-bot-that-allows-us-to-sleep/)中使用它。但是,我們能否進入下一個層次並將其用作我們最喜歡的作業系統 Linux 的指令碼語言呢?## 為什麼考慮將 Go 作為指令碼語言簡短點的回答:為什麼不呢?Go 相對容易學習,不冗餘並且有一個強大的生態庫,這些庫可以重複使用避免我們從頭開始編寫所有代碼。它可能帶來的一些其他潛在優勢:* 為你的 Go 項目提供一個基於 Go 的構建系統:`go build` 命令主要適用於小型自包含項目。更複雜的項目通常採用構建系統或指令碼集。為什麼不用 Go 編寫這些指令碼呢?* 便於使用的非特權包管理:如果你想在指令碼中使用第三方庫,你可以簡單的使用 `go get` 命令來擷取。而且由於拉取的代碼將安裝在你的 `GOPATH` 中,使用一些第三方庫並不需要系統管理員的許可權(與其他一些指令碼語言不同)。這在大型企業環境中尤其有用。* 在早期項目階段進行快速的代碼原型設計:當您進行第一次代碼迭代時,通常需要進行大量的編輯,甚至進行編譯,而且您必須在 "編輯->構建->檢查" 迴圈中浪費大量的按鍵。相反,使用 Go,您可以跳過 `build` 部分,並立即執行源檔案。* 強型別的指令碼語言:如果你在指令碼中的某個地方有個小的輸入錯誤,大多數的指令碼語言都會執行到有錯誤的地方然後停止。這可能會讓你的系統處於不一致的狀態(因為有些語句的執行會改變資料的狀態,從而汙染了執行指令碼之前的狀態)。使用強型別語言時,許多拼字錯誤可以在編譯時間被捕獲,因此有 bug 的指令碼將不會首先運行。## Go 指令碼的目前狀態咋一看 Go 指令碼貌似很容易實現 Unix 指令碼的 shebang(#! ...) 支援。[shebang 行](https://en.wikipedia.org/wiki/Shebang_(Unix))是指令碼的第一行,以 `#!` 開頭,並指定指令碼解譯器用於執行指令碼(例如,`#!/bin/bash` 或 `#!/usr/bin/env python`),所以無論使用何種程式設計語言,系統都確切知道如何執行指令碼。Go 已經使用 `go run` 命令支援 `.go` 檔案的類似於解譯器的調用,所以只需要添加適當的 shebang 行(`#!/usr/bin/env go run`)到任何的 `.go` 檔案中,設定好檔案的可執行狀態,然後就可以愉快的玩耍了。但是,直接使用 go run 還是有問題的。[這篇牛 b 的文章](https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd)詳細描述了圍繞 `go run` 的所有問題和潛在解決方案,但其要點是:* `go run` 不能正確地將指令碼錯誤碼返回給作業系統,這對指令碼很重要,因為錯誤碼是多個指令碼之間相互互動和作業系統環境最常見的方式之一。* 你不能在有效 `.go` 檔案中建立一個 shebang 行,因為 Go 語言不知道如何處理以 `#` 開頭的行。而其他語言不存在這個問題,是由於 `#` 大多數情況下是一種注釋的方式,所以最後解譯器會忽略掉 shebang 行,但是 Go 注釋是以 `//` 開頭的並且在調用時運行會產生如下錯誤:```package main:helloscript.go:1:1: illegal character U+0023 '#'```[這篇文章](https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd)描述了上述問題的幾種解決方案,包括使用一個自訂的封裝程式 [gorun](https://github.com/erning/gorun) 作為解譯器,但是都沒有提供一個理想的解決方案。你可以:* 必須使用非標準的 shebang 行,它以 `//` 開頭。這在技術上甚至不是 shebang 行,而是 bash shell 如何處理可執行文字檔的方式,所以這個解決方案是 bash 特有的。另外,由於 `go run` 的具體行為,這一行相當複雜並且不夠明顯(請參閱原始文章的樣本)。* 必須在 shebang 行中使用 gorun 自訂封裝程式,這很好,但是,最終得到的 `.go` 檔案由於非法的 `#` 字元而不能與標準 `go build` 命令編譯。## Linux 如何執行檔案OK,看起來 shebang 的方法並沒有為我們提供全面的解決方案。是否還有其他方式是我們可以使用的?讓我們仔細看看 Linux 核心如何執行二進位檔案。 當你嘗試執行一個二進位/指令碼(或任何有可執行位設定的檔案)時,你的 shell 最後只會使用 Linux `execve` 系統調用,將它傳遞給二進位檔案系統路徑,命令列參數和 當前定義的環境變數。 然後核心負責正確解析檔案並用檔案中的代碼建立一個新進程。 我們中的大多數人都知道 Linux (和許多其他類 Unix 作業系統)為其可執行檔使用 ELF 二進位格式。然而,Linux 核心開發的核心原則之一是避免任何子系統的 “vendor/format lock-in”,這是核心的一部分。因此,Linux 實現了一個“可插拔”系統,它允許核心支援任何二進位格式 - 所有你需要做的就是編寫一個正確的模組,它可以解析你選擇的格式。如果仔細研究核心原始碼,你會發現 Linux 支援更多的二進位格式。例如,最近的`4.14` Linux 核心,我們可以看到它至少支援7種二進位格式(用於各種二進位格式的樹內模組通常在其名稱中具有 `binfmt_` 首碼)。值得注意的是 [binfmt_script](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/fs/binfmt_script.c?h=linux-4.14.y) 模組,它負責解析上面提到的 shebang 行並在目標系統上執行指令碼(並不是每個人都知道 shebang 支援實際上是在核心本身而不是在 shell 或其他守護進程/進程中實現的)。## 從使用者空間擴充受支援的二進位格式但既然我們認為 shebang 不是 Go 指令碼的最佳選擇,似乎我們需要別的東西。令人驚訝的是,Linux 核心已經有了一個“其他類型的”二進位支援模組,它有一個貼切的名稱 `binfmt_misc`。該模組允許管理員通過定義良好的 `procfs` 介面直接從使用者空間動態添加對各種可執行格式的支援,並且有詳細記錄。讓我們按照[文檔](https://www.kernel.org/doc/html/v4.14/admin-guide/binfmt-misc.html)並嘗試為 `.go` 檔案設定二進位格式描述。首先,該指南告訴您將特殊的 `binfmt_misc` 檔案系統安裝到 `/proc/sys/fs/binfmt_misc`。如果您使用的是基於 systemd 的相對較新的 Linux 發行版,則很可能已經為您安裝了檔案系統,因為預設情況下 system 會為此安裝特殊的 mount 和 automount 單元。 要仔細檢查,只需運行:```shell$ mount | grep binfmt_miscsystemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=27,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)```另一種方法是檢查 `/proc/sys/fs/binfmt_misc` 中是否有檔案:正確安裝 `binfmt_misc` 檔案系統將至少建立兩個名稱為 `register` 和 `status` 的特殊檔案。接下來,因為我們希望我們的 .go 指令碼能夠正確地將結束代碼傳遞給作業系統,所以我們需要將定製的 gorun 封裝器作為我們的“解譯器”:```shell$ go get github.com/erning/gorun$ sudo mv ~/go/bin/gorun /usr/local/bin/```從技術角度上講,我們不需要將 gorun 移動到 `/usr/local/bin` 或任何其他系統路徑,而無論如何 `binfmt_misc` 都需要解譯器的完整路徑,但系統可以以任意許可權運行此可執行檔,因此從安全視角來看限制檔案存取權限是一個好主意。在這一點上,讓我們來建一個簡單的 go 指令碼 `helloscript.go` 並驗證我們可以成功“解釋”它。指令碼如下:```gopackage mainimport ("fmt""os")func main() {s := "world"if len(os.Args) > 1 {s = os.Args[1]}fmt.Printf("Hello, %v!", s)fmt.Println("")if s == "fail" {os.Exit(30)}}```檢查參數傳遞和錯誤處理是否按預期工作:```shell$ gorun helloscript.goHello, world!$ echo $?0$ gorun helloscript.go gopherHello, gopher!$ echo $?0$ gorun helloscript.go failHello, fail!$ echo $?30```現在我們需要告訴 `binfmt_misc` 模組如何使用 `gorun` 執行 `.go` 檔案。按照文檔中的描述我們需要配置如下字串: `:golang:E::go::/usr/local/bin/gorun:OC`,意思是告訴系統:當遇到以 `.go` 為副檔名的可執行檔,請使用 `/usr/local/bin/gorun` 解譯器執行該檔案。字串末尾的 `OC` 標誌確保指令碼將根據指令碼本身設定的所有者資訊和許可權位執行,而不是在解譯器二進位檔案上設定的那些位。這使 Go 指令碼的執行行為與 Linux 中其他可執行檔和指令碼的行為相同。讓我們註冊我們新的 Go 指令碼二進位格式:```shell$ echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register:golang:E::go::/usr/local/bin/gorun:OC```如果系統成功註冊了,則應在 `/proc/sys/fs/binfmt_misc` 目錄下顯示新的 golang 檔案。 最後,我們可以在本地執行我們的 .go 檔案:```shell$ chmod u+x helloscript.go$ ./helloscript.goHello, world!$ ./helloscript.go gopherHello, gopher!$ ./helloscript.go failHello, fail!$ echo $?30```就這樣了!現在我們可以根據自己的喜好編輯 helloscript.go,並在下次執行檔案時看到更改將立即可見。此外,和此前的 shebang 方式不同,我們可以隨時使用 `go build` 將檔案編譯成真正的可執行檔。
via: https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/
作者:Ignat Korchagin 譯者:shniu 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1717 次點擊