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ì)話: 類(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ì)話,每次執(zhí)行命令時(shí)都會(huì)創(chuàng)建一個(gè)新的會(huì)話 3、每次 session.Run() 或 session.Start() 執(zhí)行命令時(shí),都會(huì)用新的會(huì)話來(lái)執(zhí)行不同的命令 這些會(huì)話共享底層的 SSH 連接,但是它們獨(dú)立執(zhí)行命令 4、當(dāng)某個(gè)會(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ì)話
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ì)話中執(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ì)話
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ì)話
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ì)話下并行的運(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ì)話
session, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Close()
// 在ssh會(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ì)話中執(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ì)話
session, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Close()
// 在ssh會(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ì)話
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ì)話用于第二個(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ì)話
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ù)共享資源(如管道)的訪問(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)的訪問(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ì)話、關(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-01
Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介
日志是任何軟件的重要組成部分,Go?提供了一個(gè)內(nèi)置日志包(slog),在本文中,小編將簡(jiǎn)單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下2023-09-09
go語(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

