本文目錄一覽:
如何讓命令行程序支持管道和重定向輸入
管道和重定向輸入的數據都是通過標準輸入傳入程序的, os.Stdin 即為標準輸入。
可以通過 golang.org/x/crypto/ssh/terminal 的 terminal.IsTerminal(0) 判斷是否是管道和重定向輸入,為什麼是 0 :因為標準輸入的文件描述符是 0
為 true 時表示是交互式環境
為 false 時是我們要的場景
首先需要安裝 golang.org/x/crypto/ssh/terminal 這個包(安裝時需要 VPN 的輔助):
go get golang.org/x/crypto/ssh/terminal
也可以使用 github.com/mattn/go-isatty 這個包:
!isatty.IsTerminal(os.Stdin.Fd())
下面是示例代碼:
package mainimport (
“flag”
“fmt”
“io/ioutil”
“os”
“strings”
“golang.org/x/crypto/ssh/terminal”)func main() {
flag.Parse()
data := flag.Args()
if !terminal.IsTerminal(0) {
b, err := ioutil.ReadAll(os.Stdin)
if err == nil {
data = append(data, string(b))
}
}
fmt.Println(strings.Join(data, ” “))}
測試效果:
$ echo “hello” hello.txt
$ go run main.go hello world # 參數輸入hello world
$ cat hello.txt | go run main.go # 管道輸入hello
$ go run main.go hello.txt # 重定向輸入hello
$
如何使用Go語言實現遠程執行命令
一般命令
所謂一般命令,就是在一定時間內會執行完的命令。比如 grep, cat 等等。 執行命令的步驟是:連接,執行,獲取結果
連接
連接包含了認證,可以使用 password 或者 sshkey 2種方式來認證。下面的示例為了簡單,使用了密碼認證的方式來完成連接。
import (
“fmt”
“time”
“golang.org/x/crypto/ssh”
)
func connect(user, password, host string, port int) (*ssh.Session, error) {
var (
auth []ssh.AuthMethod
addr string
clientConfig *ssh.ClientConfig
client *ssh.Client
session *ssh.Session
err error
)
// get auth method
auth = make([]ssh.AuthMethod, 0)
auth = append(auth, ssh.Password(password))
clientConfig = ssh.ClientConfig{
User: user,
Auth: auth,
Timeout: 30 * time.Second,
}
// connet to ssh
addr = fmt.Sprintf(“%s:%d”, host, port)
if client, err = ssh.Dial(“tcp”, addr, clientConfig); err != nil {
return nil, err
}
// create session
if session, err = client.NewSession(); err != nil {
return nil, err
}
return session, nil
}
連接的方法很簡單,只要提供登錄主機的 用戶*, *密碼*, *主機名或者IP*, *SSH端口
執行,命令獲取結果
連接成功後,執行命令很簡單
import (
“fmt”
“log”
“os”
“time”
“golang.org/x/crypto/ssh”
)
func main() {
session, err := connect(“root”, “xxxxx”, “127.0.0.1”, 22)
if err != nil {
log.Fatal(err)
}
defer session.Close()
session.Run(“ls /; ls /abc”)
}
上面代碼運行之後,雖然命令正常執行了,但是沒有正常輸出的結果,也沒有異常輸出的結果。 要想顯示結果,需要將 session 的 Stdout 和 Stderr 重定向 修改 func main 為如下:
func main() {
session, err := connect(“root”, “xxxxx”, “127.0.0.1”, 22)
if err != nil {
log.Fatal(err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Run(“ls /; ls /abc”)
}
這樣就能在屏幕上顯示正常,異常的信息了。
交互式命令
上面的方式無法遠程執行交互式命令,比如 top , 遠程編輯一個文件,比如 vi /etc/nginx/nginx.conf 如果要支持交互式的命令,需要當前的terminal來接管遠程的 PTY。
func main() {
session, err := connect(“root”, “olordjesus”, “dockers.iotalabs.io”, 2210)
if err != nil {
log.Fatal(err)
}
defer session.Close()
fd := int(os.Stdin.Fd())
oldState, err := terminal.MakeRaw(fd)
if err != nil {
panic(err)
}
defer terminal.Restore(fd, oldState)
// excute command
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
panic(err)
}
// Set up terminal modes
modes := ssh.TerminalModes{
ssh.ECHO: 1, // enable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty(“xterm-256color”, termHeight, termWidth, modes); err != nil {
log.Fatal(err)
}
session.Run(“top”)
}
golang 進程創建,fork,以及熱重啟(無縫升級)
一般來說,進程的操作使用的是一些系統的命令,所以go內部使用os包,進行一些運行系統命令的操作
os 包及其子包 os/exec 提供了創建進程的方法。
一般的,應該優先使用 os/exec 包。因為 os/exec 包依賴 os 包中關鍵創建進程的 API,為了便於理解,我們先探討 os 包中和進程相關的部分。
Unix :fork創建一個進程,(及其一些變種,如 vfork、clone)。
Go:Linux 下創建進程使用的系統調用是 clone。
允許一進程(父進程)創建一新進程(子進程)。具體做法是,新的子進程幾近於對父進程的翻版:子進程獲得父進程的棧、數據段、堆和執行文本段的拷貝。可將此視為把父進程一分為二。
終止一進程,將進程佔用的所有資源(內存、文件描述符等)歸還內核,交其進行再次分配。參數 status 為一整型變量,表示進程的退出狀態。父進程可使用系統調用 wait() 來獲取該狀態。
目的有二:其一,如果子進程尚未調用 exit() 終止,那麼 wait 會掛起父進程直至子進程終止;其二,子進程的終止狀態通過 wait 的 status 參數返回。
加載一個新程序(路徑名為 pathname,參數列表為 argv,環境變量列表為 envp)到當前進程的內存。這將丟棄現存的程序文本段,並為新程序重新創建棧、數據段以及堆。通常將這一動作稱為執行一個新程序。
沒有直接提供 fork 系統調用的封裝,而是將 fork 和 execve 合二為一,提供了 syscall.ForkExec。如果想只調用 fork,得自己通過 syscall.Syscall(syscall.SYS_FORK, 0, 0, 0) 實現。
os.Process 存儲了通過 StartProcess 創建的進程的相關信息。
一般通過 StartProcess 創建 Process 的實例,函數聲明如下:
它使用提供的程序名、命令行參數、屬性開始一個新進程。StartProcess 是一個低級別的接口。os/exec 包提供了高級別的接口,一般應該盡量使用 os/exec 包。如果出錯,錯誤的類型會是 *PathError。
屬性定義如下:
FindProcess 可以通過 pid 查找一個運行中的進程。該函數返回的 Process 對象可以用於獲取關於底層操作系統進程的信息。在 Unix 系統中,此函數總是成功,即使 pid 對應的進程不存在。
Process 提供了四個方法:Kill、Signal、Wait 和 Release。其中 Kill 和 Signal 跟信號相關,而 Kill 實際上就是調用 Signal,發送了 SIGKILL 信號,強制進程退出,關於信號,後續章節會專門講解。
Release 方法用於釋放 Process 對象相關的資源,以便將來可以被再使用。該方法只有在確定沒有調用 Wait 時才需要調用。Unix 中,該方法的內部實現只是將 Process 的 pid 置為 -1。
通過 os 包可以做到運行外部命令,如前面的例子。不過,Go 標準庫為我們封裝了更好用的包: os/exec,運行外部命令,應該優先使用它,它包裝了 os.StartProcess 函數以便更容易的重定向標準輸入和輸出,使用管道連接 I/O,以及作其它的一些調整。
exec.LookPath 函數在 PATH 指定目錄中搜索可執行程序,如 file 中有 /,則只在當前目錄搜索。該函數返回完整路徑或相對於當前路徑的一個相對路徑。
func LookPath(file string) (string, error)
如果在 PATH 中沒有找到可執行文件,則返回 exec.ErrNotFound。
Cmd 結構代表一個正在準備或者在執行中的外部命令,調用了 Run、Output 或 CombinedOutput 後,Cmd 實例不能被重用。
一般的,應該通過 exec.Command 函數產生 Cmd 實例:
用法
得到 * Cmd 實例後,接下來一般有兩種寫法:
前面講到,通過 Cmd 實例後,有兩種方式運行命令。有時候,我們不只是簡單的運行命令,還希望能控制命令的輸入和輸出。通過上面的 API 介紹,控制輸入輸出有幾種方法:
參考資料:
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/295512.html