golang標(biāo)準(zhǔn)庫(kù)SSH操作示例詳解
前言
SSH 全稱(chēng)為 Secure Shell,是一種用于安全地遠(yuǎn)程登錄到網(wǎng)絡(luò)上的其他計(jì)算機(jī)的網(wǎng)絡(luò)協(xié)議。相信做運(yùn)維的同學(xué)沒(méi)有不了解 SSH的,比較常用的登錄服務(wù)器的 shell 工具例如 Xshell、SecureCRT、iTerm2 等都是基于 SSH 協(xié)議實(shí)現(xiàn)的。Golang 中的的 crypto/ssh 庫(kù)提供了實(shí)現(xiàn) SSH 客戶(hù)端的功能,本文接下來(lái)詳細(xì)講解下如何使用 Golang 實(shí)現(xiàn)操作 SSH 客戶(hù)端,為后續(xù)運(yùn)維開(kāi)發(fā)的道路上使用golang編寫(xiě)腳本先夯實(shí)一下基礎(chǔ)
一、了解SSH
在Golang中,有幾個(gè)常用的SSH庫(kù),如golang.org/x/crypto/ssh和github.com/go-ssh/ssh。 本次將重點(diǎn)介紹golang.org/x/crypto/ssh,因?yàn)樗怯蒅o官方維護(hù)的. SSH庫(kù)功能分類(lèi): SSH客戶(hù)端: 允許用戶(hù)通過(guò)SSH協(xié)議連接到遠(yuǎn)程服務(wù)器。 SSH服務(wù)器: 允許遠(yuǎn)程用戶(hù)通過(guò)SSH協(xié)議連接到本地服務(wù)器。 命令執(zhí)行: 在遠(yuǎn)程服務(wù)器上執(zhí)行命令。 文件傳輸: 在本地和遠(yuǎn)程服務(wù)器之間傳輸文件。 交會(huì)時(shí)會(huì)話(huà): 類(lèi)比xshell,當(dāng)代碼執(zhí)行后,如同在操作真實(shí)的xshell一樣
二、重要知識(shí)點(diǎn)
1.安裝ssh庫(kù)
代碼如下(示例):
go get golang.org/x/crypto/ssh
2.ssh庫(kù)重要知識(shí)牢記
結(jié)合演示代碼一起更好理解
如下(示例):
1、client 對(duì)象(SSH 客戶(hù)端)在整個(gè)程序中只創(chuàng)建一次 2、可以通過(guò) client.NewSession() 多次創(chuàng)建多個(gè) session 對(duì)象.每個(gè) session 是一個(gè)獨(dú)立的會(huì)話(huà),每次執(zhí)行命令時(shí)都會(huì)創(chuàng)建一個(gè)新的會(huì)話(huà) 3、每次 session.Run() 或 session.Start() 執(zhí)行命令時(shí),都會(huì)用新的會(huì)話(huà)來(lái)執(zhí)行不同的命令 這些會(huì)話(huà)共享底層的 SSH 連接,但是它們獨(dú)立執(zhí)行命令 4、當(dāng)某個(gè)會(huì)話(huà)的命令執(zhí)行完畢,必須調(diào)用session.Close() 釋放相關(guān)資源。 5、切記不能在同一個(gè) session 上并行執(zhí)行多個(gè)命令。如果需要并行執(zhí)行多個(gè)命令,應(yīng)該創(chuàng)建多個(gè) session
演示代碼(示例):
package main import ( "fmt" "golang.org/x/crypto/ssh" "log" ) func main() { // SSH 配置 config := &ssh.ClientConfig{ User: "root", // 替換為遠(yuǎn)程服務(wù)器的用戶(hù)名 Auth: []ssh.AuthMethod{ ssh.Password("1"), // 替換為遠(yuǎn)程服務(wù)器密碼 }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主機(jī)密鑰驗(yàn)證 } // 連接遠(yuǎn)程服務(wù)器 client, err := ssh.Dial("tcp", "192.168.56.160:22", config) // 替換為遠(yuǎn)程服務(wù)器的IP地址 if err != nil { log.Fatalf("Failed to dial: %v", err) } defer client.Close() // 創(chuàng)建第一個(gè)會(huì)話(huà) session1, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session 1: %v", err) } defer session1.Close() // 執(zhí)行第一個(gè)命令 fmt.Println("Executing command on session 1-1") err = session1.Run("echo Hello from session 1-1") if err != nil { log.Fatalf("Failed to run command on session 1-1: %v", err) } // 演示在第一個(gè)會(huì)話(huà)中執(zhí)行第二個(gè)命令看是否能成功 fmt.Println("Executing command on session 1-2") err = session1.Run("echo Hello from session 1-2") if err != nil { log.Fatalf("Failed to run command on session 1-2: %v", err) } // 創(chuàng)建第二個(gè)會(huì)話(huà) session2, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session 2: %v", err) } defer session2.Close() // 執(zhí)行第二個(gè)命令 fmt.Println("Executing command on session 2") err = session2.Run("echo Hello from session 2") if err != nil { log.Fatalf("Failed to run command on session 2: %v", err) } // 創(chuàng)建第三個(gè)會(huì)話(huà) session3, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session 3: %v", err) } defer session3.Close() // 執(zhí)行第三個(gè)命令 fmt.Println("Executing command on session 3") err = session3.Run("echo Hello from session 3") if err != nil { log.Fatalf("Failed to run command on session 3: %v", err) } fmt.Println("All commands executed successfully") }
執(zhí)行這段代碼,返回如下所示,在同一個(gè)會(huì)話(huà)下并行的運(yùn)行兩條命令,發(fā)現(xiàn)運(yùn)行失敗
當(dāng)將1-2這段代碼注釋掉后,再次運(yùn)行代碼可以成功運(yùn)行,跟上述的描述一致
三、模擬連接遠(yuǎn)程服務(wù)器并執(zhí)行命令
演示怎么在golang中使用SSH庫(kù)連接服務(wù)器并執(zhí)行相應(yīng)的linux命令
package main import ( "golang.org/x/crypto/ssh" "log" ) func main() { // 創(chuàng)建SSH配置--密碼認(rèn)證 config := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ ssh.Password("1"), //密碼認(rèn)證 }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // 創(chuàng)建SSH配置--SSH密鑰認(rèn)證(生產(chǎn)環(huán)境下建議采用該方式) 二選一即可 //config := &ssh.ClientConfig{ //User: "username", //Auth: []ssh.AuthMethod{ // ssh.PublicKeysFromFile("path/to/private/key", "path/to/public/key"), //}, // HostKeyCallback: ssh.FixedHostKey(hostKey), //} // 連接到遠(yuǎn)程服務(wù)器,并返回一個(gè)ssh客戶(hù)端實(shí)例, /* 返回值類(lèi)型: *ssh.Client error */ client, err := ssh.Dial("tcp", "192.168.56.160:22", config) if err != nil { log.Fatalf("Failed to dial: %v", err) } defer client.Close() // 使用客戶(hù)端創(chuàng)建一個(gè)ssh會(huì)話(huà) session, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session: %v", err) } defer session.Close() // 在ssh會(huì)話(huà)中執(zhí)行命令并輸出命令結(jié)果 out, err := session.CombinedOutput("ls /export") if err != nil { log.Fatalf("Failed to run: %v", err) } log.Printf("%s", out) }
四、SSH與os/exec標(biāo)準(zhǔn)庫(kù)下執(zhí)行命令的幾種方式對(duì)比
方法 | 功能描述 | 阻塞/非阻塞 | 輸出捕獲 | 使用場(chǎng)景 |
---|---|---|---|---|
cmd:=exec.Command(“xx”,“x”) err:=cmd.Run() | 執(zhí)行本地命令并等待命令完成,返回錯(cuò)誤 | 阻塞 | 不捕獲輸出(需用 Output/CombinedOutput 捕獲) | 本地命令執(zhí)行,等待命令完成 |
err:=newsession.Run("xxx") | 執(zhí)行遠(yuǎn)程命令并等待命令完成,返回錯(cuò)誤 | 阻塞 | 不捕獲輸出(需手動(dòng)捕獲) | 遠(yuǎn)程 SSH 命令執(zhí)行,等待完成 |
cmd:=exec.Command(“xx”,“xx”) cmd.Start() | 啟動(dòng)本地命令異步執(zhí)行,不等待命令完成 | 非阻塞,如果要阻塞,使用exec.Command().Wait()實(shí)現(xiàn) | 可通過(guò) Stdout、 Stderr 獲取輸出 | 本地命令異步執(zhí)行,非阻塞 |
err:=newsession.Start("xx") | 啟動(dòng)遠(yuǎn)程命令異步執(zhí)行,不等待命令完成 | 非阻塞,適用于需要啟動(dòng)后臺(tái)進(jìn)程的場(chǎng)景,如果要阻塞使用,newsession.Wait()實(shí)現(xiàn) | 可通過(guò) Stdout、 Stderr 獲取輸出 | 遠(yuǎn)程命令異步執(zhí)行,非阻塞 |
cmd:=exec.Command(“xx”,“x”) out,err:=cmd.CombinedOutput() | 執(zhí)行本地命令并捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤的合并輸出 | 阻塞 | 捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤的合并輸出 | 本地命令執(zhí)行,捕獲所有輸出 |
out,err:=newsession.CombinedOutput("xx") | 執(zhí)行遠(yuǎn)程命令并捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤的合并輸出 | 阻塞 | 捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤的合并輸出 | 遠(yuǎn)程命令執(zhí)行,捕獲所有輸出 |
五、SSH庫(kù)下三種執(zhí)行命令方式演示
5.1. session.CombinedOutput()示例
連接192.168.56.160服務(wù)器,并執(zhí)行l(wèi)s /var/log/命令查看目錄下的文件
注意事項(xiàng): 1、CombinedOutput()函數(shù)剖析 func (s *ssh.Session) CombinedOutput(cmd string) ([]byte, error) 接收參數(shù)類(lèi)型 string 返回值類(lèi)型[]byte,error 將[]byte轉(zhuǎn)換為string類(lèi)型輸出的結(jié)果為命令的執(zhí)行結(jié)果 2、在一個(gè)session會(huì)話(huà)中執(zhí)行多條命令的操作 將多條命令保存在切片中,然后for循環(huán)將命令(value)傳遞給CombinedOutput()函數(shù)即可 // 示例命令 commands := []string{"ls -l /tmp", "uptime", "df -h"} for _, command := range commands { executeCommand(client, command, ip, resultChan, &mu) } out, err := session.CombinedOutput(commands)
package main import ( "golang.org/x/crypto/ssh" "log" ) func main() { // 創(chuàng)建SSH配置--密碼認(rèn)證 config := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ ssh.Password("1"), //密碼認(rèn)證 }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // 連接到遠(yuǎn)程服務(wù)器,并返回一個(gè)ssh客戶(hù)端實(shí)例 client, err := ssh.Dial("tcp", "192.168.56.160:22", config) if err != nil { log.Fatalf("Failed to dial: %v", err) } defer client.Close() // 使用客戶(hù)端創(chuàng)建一個(gè)ssh會(huì)話(huà) session, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session: %v", err) } defer session.Close() // 在ssh會(huì)話(huà)中執(zhí)行命令并輸出命令結(jié)果。 out, err := session.CombinedOutput("ls /var/log/") if err != nil { log.Fatalf("Failed to run: %v", err) } log.Printf("out:%s\n", out) }
5.2. session.Run()示例
注意事項(xiàng): session.Run(cmd string )error func (s *ssh.Session) Run(cmd string) error 接收參數(shù)類(lèi)型 string 返回類(lèi)型 error <如果要想獲取到執(zhí)行的結(jié)果和錯(cuò)誤,即區(qū)分標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,則使用下方的方法>
package main import ( "bytes" "fmt" "log" "golang.org/x/crypto/ssh" ) // setupSSHClient 配置并返回一個(gè)SSH客戶(hù)端 func setupSSHClient(user, password, host string, port int) (*ssh.Client, error) { config := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ ssh.Password(password), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 注意:這里使用了不安全的回調(diào),僅用于示例。在實(shí)際應(yīng)用中,你應(yīng)該驗(yàn)證主機(jī)密鑰。 } client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), config) if err != nil { return nil, err } return client, nil } func main() { user := "root" password := "1" host := "192.168.56.162" port := 22 // 默認(rèn)SSH端口是22 client, err := setupSSHClient(user, password, host, port) if err != nil { log.Fatalf("Failed to setup SSH client: %v", err) } defer client.Close() if host == "192.168.56.162" { newsession, _ := client.NewSession() defer newsession.Close() //Run() // 創(chuàng)建一個(gè)緩沖區(qū)來(lái)捕獲命令的輸出 var outputBuf bytes.Buffer // 將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都重定向到同一個(gè)緩沖區(qū) newsession.Stdout = &outputBuf newsession.Stderr = &outputBuf err := newsession.Run("ls /var/log/audit/") if err != nil { // 輸出執(zhí)行命令時(shí)的錯(cuò)誤 fmt.Printf("Error executing command: %v\n", err) } // 打印命令的輸出(包括標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤) fmt.Printf("Command output:\n%s\n", outputBuf.String()) } }
5.3. session.Start()、session.Wait()示例
注意事項(xiàng): func (s *ssh.Session) Start(cmd string) error 接收參數(shù)類(lèi)型 string 返回類(lèi)型 error 如果要想獲取到執(zhí)行的結(jié)果和錯(cuò)誤,即區(qū)分標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,則使用下方的方法 func (s *ssh.Session) Wait() error 返回類(lèi)型 error 等待 session.Start() 單獨(dú)使用時(shí),命令會(huì)在后臺(tái)執(zhí)行,程序不會(huì)等待命令的完成,立即繼續(xù)執(zhí)行后續(xù)代碼。 session.Start() 和 session.Wait() 一起使用時(shí),程序會(huì)在 Wait() 處等待命令執(zhí)行完成,之后才會(huì)繼續(xù)執(zhí)行后續(xù)的代碼。
package main import ( "fmt" "golang.org/x/crypto/ssh" "log" "time" ) func main() { // SSH 配置 config := &ssh.ClientConfig{ User: "root", // 替換為遠(yuǎn)程服務(wù)器的用戶(hù)名 Auth: []ssh.AuthMethod{ ssh.Password("1"), // 替換為遠(yuǎn)程服務(wù)器密碼 }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主機(jī)密鑰驗(yàn)證 } // 連接遠(yuǎn)程服務(wù)器 client, err := ssh.Dial("tcp", "192.168.56.160:22", config) // 替換為遠(yuǎn)程服務(wù)器的IP地址 if err != nil { log.Fatalf("Failed to dial: %v", err) } defer client.Close() // 創(chuàng)建會(huì)話(huà) session, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session: %v", err) } defer session.Close() // 示例 1:使用 session.Start() 啟動(dòng)命令,但不等待 fmt.Println("=== 示例 1: 使用 session.Start() 啟動(dòng)命令,不等待 ===") err = session.Start("sleep 5") // 啟動(dòng)一個(gè)后臺(tái)命令 if err != nil { log.Fatalf("Failed to start command: %v", err) } // 程序不會(huì)等待 sleep 5 執(zhí)行完成,立即繼續(xù)執(zhí)行下一行 fmt.Println("命令已啟動(dòng),程序繼續(xù)執(zhí)行,不等待命令結(jié)束") // 等待一段時(shí)間,觀察命令是否執(zhí)行完 time.Sleep(2 * time.Second) fmt.Println("程序在等待2秒后繼續(xù)執(zhí)行。") // 示例 2:使用 session.Start() 啟動(dòng)命令,并等待命令執(zhí)行完畢 // 創(chuàng)建新的會(huì)話(huà)用于第二個(gè)命令 session2, err := client.NewSession() if err != nil { log.Fatalf("Failed to create session for second command: %v", err) } defer session2.Close() fmt.Println("\n=== 示例 2: 使用 session.Start() 啟動(dòng)命令,并調(diào)用 session.Wait() 等待 ===") err = session2.Start("sleep 5") // 啟動(dòng)一個(gè)后臺(tái)命令 if err != nil { log.Fatalf("Failed to start second command: %v", err) } // 程序會(huì)在這里等待命令執(zhí)行完成 err = session2.Wait() // 等待命令完成 if err != nil { log.Fatalf("Failed to wait for command to finish: %v", err) } fmt.Println("命令執(zhí)行完成,程序繼續(xù)執(zhí)行") // 結(jié)束 fmt.Println("\n所有命令已執(zhí)行完畢") }
六、兩種捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤的方法:
StdoutPipe
/ StderrPipe
和 session.Stdout
/ session.Stderr
之間的區(qū)別
6.1. 使用 StdoutPipe 和 StderrPipe捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤
重要代碼示例
// 獲取 標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤 stdout, _ := session.StdoutPipe() output := make([]byte, 1024) for { n, err := stdout.Read(output) if err != nil { break } fmt.Sprintf("STDOUT from %s: %s", ip, string(output[:n])) } stderr, err := session.StderrPipe() output := make([]byte, 1024) for { n, err := stderr.Read(output) if err != nil { break } fmt.Sprintf("STDERR from %s: %s", ip, string(output[:n])) }
解釋
1. 使用 `StdoutPipe` 和 `StderrPipe`: - `StdoutPipe()` 和 `StderrPipe()` 返回一個(gè) `io.Reader`,可以用來(lái)讀取遠(yuǎn)程命令的標(biāo)準(zhǔn)輸出(stdout)和標(biāo)準(zhǔn)錯(cuò)誤輸出(stderr) - 可以通過(guò)從這些管道中讀取數(shù)據(jù)來(lái)獲取命令的輸出,通常會(huì)使用協(xié)程來(lái)異步讀取這些管道中的數(shù)據(jù) 2. 工作原理: - 首先通過(guò) `session.StdoutPipe()` 和 `session.StderrPipe()` 獲取輸出的管道(`io.Reader`) - 然后在程序中手動(dòng)讀取這些管道的內(nèi)容,通常通過(guò) `io.Copy` 或者 `bufio.Reader` 來(lái)處理流。 - 這種方式適用于需要處理較大輸出或需要實(shí)時(shí)讀取命令輸出的場(chǎng)景。 3. 優(yōu)點(diǎn): - 可以實(shí)時(shí)讀取輸出,因?yàn)楣艿朗浅掷m(xù)開(kāi)放的,適合需要處理大量數(shù)據(jù)或逐行輸出的情況。 - 可以分別處理標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,提供更多靈活性。 4. 缺點(diǎn): - 需要異步讀取標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,可能需要更多的代碼來(lái)確保并發(fā)處理和同步。 - 適用于需要實(shí)時(shí)處理輸出的場(chǎng)景,不適合簡(jiǎn)單的命令輸出捕獲。
6.2. 使用 重定向
session.Stdout
和 session.Stderr
捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤
重要代碼示例
.... // 創(chuàng)建一個(gè)緩沖區(qū)來(lái)捕獲命令的輸出 var outputBuf bytes.Buffer // 將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都重定向到同一個(gè)緩沖區(qū) session.Stdout = &outputBuf session.Stderr = &outputBuf err := newsession.Run("ls /var/log/audit/") if err != nil { // 輸出執(zhí)行命令時(shí)的錯(cuò)誤 fmt.Printf("Error executing command: %v\n", err) } // 打印命令的輸出(包括標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤) fmt.Printf("Command output:\n%s\n", outputBuf.String()) ...
解釋
1. 使用 `newsession.Stdout` 和 `newsession.Stderr`: - `session.Stdout` 和 `session.Stderr` 分別是 `io.Writer` 類(lèi)型,允許將命令的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤直接寫(xiě)入一個(gè)緩沖區(qū)(如 `bytes.Buffer`)。 - 可以通過(guò) `outputBuf.String()` 獲取完整的命令輸出。這里,`Stdout` 和 `Stderr` 都被重定向到同一個(gè) `bytes.Buffer`, - 這樣就能捕獲命令的所有輸出(無(wú)論是標(biāo)準(zhǔn)輸出還是標(biāo)準(zhǔn)錯(cuò)誤)。 2. 工作原理: - `session.Run()` 會(huì)直接執(zhí)行命令并把標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都寫(xiě)入到指定的緩沖區(qū)。 - 不需要異步讀取輸出,命令執(zhí)行完成后,只需要讀取 `outputBuf` 即可獲取所有輸出。 3. 優(yōu)點(diǎn): - 代碼簡(jiǎn)單,易于實(shí)現(xiàn),適合捕獲簡(jiǎn)單的命令輸出。 - 不需要顯式地管理異步讀取標(biāo)準(zhǔn)輸出和錯(cuò)誤流,適用于不需要實(shí)時(shí)處理輸出的場(chǎng)景。 - 適合于簡(jiǎn)單的任務(wù)(例如調(diào)試、輸出日志等)并且輸出數(shù)據(jù)量較小的情況。 4. 缺點(diǎn): - 如果命令輸出量大或者需要實(shí)時(shí)處理輸出,可能會(huì)遇到緩沖區(qū)的限制或延遲。 - 不能實(shí)時(shí)讀取輸出,必須等命令執(zhí)行完畢才能獲取所有輸出。
6.3.兩種方式的區(qū)別
1. 實(shí)時(shí)性: - `StdoutPipe` 和 `StderrPipe`: 適合實(shí)時(shí)讀取標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤??梢栽诿顖?zhí)行的過(guò)程中動(dòng)態(tài)處理輸出數(shù)據(jù)。 - `Stdout` 和 `Stderr`: 適合捕獲命令執(zhí)行后的完整輸出,并不實(shí)時(shí)讀取。如果需要完整的命令輸出,一次性獲取比較簡(jiǎn)單。 2. 使用場(chǎng)景: - `StdoutPipe` 和 `StderrPipe`: 適合輸出較大、需要流式處理的場(chǎng)景,比如你需要逐行讀取或?qū)崟r(shí)處理命令輸出的場(chǎng)景。 - `Stdout` 和 `Stderr`: 適合捕獲命令的完整輸出并一次性處理,代碼簡(jiǎn)單,適合小規(guī)模的輸出捕獲。 3. 復(fù)雜性: - `StdoutPipe` 和 `StderrPipe`: 稍微復(fù)雜,因?yàn)樾枰幚聿l(fā)讀取輸出流,可能涉及協(xié)程。 - `Stdout` 和 `Stderr`: 簡(jiǎn)單易懂,適合不需要實(shí)時(shí)讀取輸出的情況。 根據(jù)實(shí)際需求,可以選擇適合的方式: 如果需要并發(fā)處理或?qū)崟r(shí)處理輸出流,使用 `StdoutPipe` 和 `StderrPipe` 如果需要一次性獲取完整輸出,使用 `Stdout` 和 `Stderr` 會(huì)更加簡(jiǎn)潔。
七、示例: 連接到多臺(tái)服務(wù)器并執(zhí)行多個(gè)命令返回命令執(zhí)行結(jié)果
先看代碼再分析
package main import ( "bufio" "fmt" "log" "os" "strings" "sync" "golang.org/x/crypto/ssh" ) func executeCommand(client *ssh.Client, command string, ip string, resultChan chan<- string, mu *sync.Mutex) { // 創(chuàng)建一個(gè)新的 SSH 會(huì)話(huà) session, err := client.NewSession() if err != nil { log.Println("Failed to create session:", err) resultChan <- fmt.Sprintf("Error on %s: Failed to create session", ip) return } defer session.Close() // 獲取 Stdout 和 Stderr 輸出 stdout, err := session.StdoutPipe() if err != nil { log.Println("Failed to get StdoutPipe:", err) resultChan <- fmt.Sprintf("Error on %s: Failed to get StdoutPipe", ip) return } stderr, err := session.StderrPipe() if err != nil { log.Println("Failed to get StderrPipe:", err) resultChan <- fmt.Sprintf("Error on %s: Failed to get StderrPipe", ip) return } // 啟動(dòng)命令 err = session.Start(command) if err != nil { log.Println("Failed to start command:", err) resultChan <- fmt.Sprintf("Error on %s: Failed to start command", ip) return } // 使用鎖來(lái)確保對(duì)共享資源(如輸出的打?。┦谴械? mu.Lock() defer mu.Unlock() // 讀取命令輸出并打印到管道 go func() { output := make([]byte, 1024) for { n, err := stdout.Read(output) if err != nil { break } resultChan <- fmt.Sprintf("STDOUT from %s: %s", ip, string(output[:n])) } }() go func() { output := make([]byte, 1024) for { n, err := stderr.Read(output) if err != nil { break } resultChan <- fmt.Sprintf("STDERR from %s: %s", ip, string(output[:n])) } }() // 等待命令執(zhí)行完畢 err = session.Wait() if err != nil { log.Println("Error executing command:", err) resultChan <- fmt.Sprintf("Error on %s: %v", ip, err) } else { resultChan <- fmt.Sprintf("Command executed successfully on %s", ip) } } func main() { // 加載 IP 地址文件 file, err := os.Open("/export/test/ips.txt") if err != nil { log.Fatal("Failed to open file:", err) } defer file.Close() // 讀取 IP 地址 var ips []string scanner := bufio.NewScanner(file) for scanner.Scan() { ip := strings.TrimSpace(scanner.Text()) if ip != "" { ips = append(ips, ip) } } if err := scanner.Err(); err != nil { log.Fatal("Failed to read file:", err) } // 設(shè)置 SSH 客戶(hù)端配置,使用密碼認(rèn)證 sshConfig := &ssh.ClientConfig{ User: "root", // SSH 用戶(hù)名 Auth: []ssh.AuthMethod{ ssh.Password("1"), // 密碼認(rèn)證 }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 注意:生產(chǎn)環(huán)境中不建議使用此選項(xiàng) } // 創(chuàng)建一個(gè)管道用于接收結(jié)果 resultChan := make(chan string, len(ips)*3) // 每臺(tái)機(jī)器執(zhí)行多個(gè)命令,調(diào)整管道容量 var wg sync.WaitGroup var mu sync.Mutex // 創(chuàng)建鎖 // 遍歷 IP 地址,并為每個(gè) IP 地址啟動(dòng)一個(gè) goroutine for _, ip := range ips { wg.Add(1) go func(ip string) { defer wg.Done() // 建立 SSH 連接 client, err := ssh.Dial("tcp", ip+":22", sshConfig) if err != nil { log.Printf("Failed to connect to %s: %v", ip, err) resultChan <- fmt.Sprintf("Failed to connect to %s", ip) return } defer client.Close() // 對(duì)每臺(tái)機(jī)器執(zhí)行多個(gè)命令 commands := []string{"ls -l /tmp", "uptime", "df -h"} // 示例命令 for _, command := range commands { executeCommand(client, command, ip, resultChan, &mu) } }(ip) } // 在所有任務(wù)完成之后關(guān)閉 resultChan go func() { wg.Wait() close(resultChan) }() // 輸出所有結(jié)果 for result := range resultChan { fmt.Println(result) } }
涉及到的知識(shí)點(diǎn): 1、管道 2、互斥鎖 3、goroutine并發(fā) 4、SSH 5、session.Start/Wait 6、分開(kāi)捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤 7、按行讀取文件內(nèi)容 上述代碼示例演示了如何在多臺(tái)機(jī)器上并發(fā)執(zhí)行多個(gè)命令,并使用 sync.Mutex 來(lái)保護(hù)共享資源(如管道)的訪(fǎng)問(wèn) 具體流程: 1、從文件中按行讀取IP并保存到切片ips中 2、設(shè)置ssh配置,從管道中讀取IP,將每個(gè)服務(wù)器連接和每個(gè)要執(zhí)行的命令都放在一個(gè) goroutine中。 主程序繼續(xù)啟動(dòng)新的 goroutine 執(zhí)行任務(wù),而不會(huì)因?yàn)槟骋慌_(tái)服務(wù)器的命令執(zhí)行而導(dǎo)致整個(gè)程序阻塞 3、將連接信息和捕獲的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤信息都寫(xiě)入到管道中 4、當(dāng)服務(wù)器連接成功后,調(diào)用執(zhí)行命令函數(shù)executeCommand,再該代碼中的鎖用于保護(hù)共享資源(resultChan)的訪(fǎng)問(wèn) 因?yàn)槿绻鄠€(gè) goroutine 同時(shí)向通道發(fā)送數(shù)據(jù)(比如日志輸出) 沒(méi)有鎖會(huì)導(dǎo)致輸出混亂(多個(gè) goroutine 的日志可能會(huì)交錯(cuò),難以看清) 使用 sync.Mutex 來(lái)確保每次只有一個(gè) goroutine 向通道發(fā)送數(shù)據(jù),從而保證輸出日志的順序和一致性 保證了多個(gè) goroutine 在寫(xiě)入 resultChan 時(shí)不會(huì)互相干擾,避免了并發(fā)寫(xiě)入導(dǎo)致的數(shù)據(jù)不一致或錯(cuò)亂 5、當(dāng)所有遠(yuǎn)程機(jī)器的命令執(zhí)行完成后,關(guān)閉會(huì)話(huà)、關(guān)閉通道,最終再打印出通道中所有的日志信息
總結(jié)
以上就是SSH標(biāo)準(zhǔn)庫(kù)自己整理的知識(shí),故不積跬步,無(wú)以至千里;不積小流,無(wú)以成江海
,慢慢整理golang中運(yùn)維可以使用到的相關(guān)庫(kù),向運(yùn)維逐漸靠攏
到此這篇關(guān)于golang標(biāo)準(zhǔn)庫(kù)SSH操作示例的文章就介紹到這了,更多相關(guān)golang標(biāo)準(zhǔn)庫(kù)SSH內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?defer?return?panic?執(zhí)行順序示例詳解
這篇文章主要介紹了go?defer?return?panic?執(zhí)行順序,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介
日志是任何軟件的重要組成部分,Go?提供了一個(gè)內(nèi)置日志包(slog),在本文中,小編將簡(jiǎn)單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下2023-09-09go語(yǔ)言接口的定義和實(shí)現(xiàn)簡(jiǎn)單分享
這篇文章主要介紹了go語(yǔ)言接口的定義和實(shí)現(xiàn)簡(jiǎn)單分享的相關(guān)資料,需要的朋友可以參考下2023-08-08使用Go語(yǔ)言創(chuàng)建error的幾種方式小結(jié)
Go語(yǔ)言函數(shù)(或方法)是支持多個(gè)返回值的,因此在Go語(yǔ)言的編程哲學(xué)中,函數(shù)的返回值的最后一個(gè)通常都是error類(lèi)型,所以本文給大家介紹了使用Go語(yǔ)言創(chuàng)建error的幾種方式小結(jié),文中通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01