golang標(biāo)準(zhǔn)庫SSH操作示例詳解
前言
SSH 全稱為 Secure Shell,是一種用于安全地遠(yuǎn)程登錄到網(wǎng)絡(luò)上的其他計算機(jī)的網(wǎng)絡(luò)協(xié)議。相信做運(yùn)維的同學(xué)沒有不了解 SSH的,比較常用的登錄服務(wù)器的 shell 工具例如 Xshell、SecureCRT、iTerm2 等都是基于 SSH 協(xié)議實現(xiàn)的。Golang 中的的 crypto/ssh 庫提供了實現(xiàn) SSH 客戶端的功能,本文接下來詳細(xì)講解下如何使用 Golang 實現(xiàn)操作 SSH 客戶端,為后續(xù)運(yùn)維開發(fā)的道路上使用golang編寫腳本先夯實一下基礎(chǔ)
一、了解SSH
在Golang中,有幾個常用的SSH庫,如golang.org/x/crypto/ssh和github.com/go-ssh/ssh。
本次將重點(diǎn)介紹golang.org/x/crypto/ssh,因為它是由Go官方維護(hù)的.
SSH庫功能分類:
SSH客戶端: 允許用戶通過SSH協(xié)議連接到遠(yuǎn)程服務(wù)器。
SSH服務(wù)器: 允許遠(yuǎn)程用戶通過SSH協(xié)議連接到本地服務(wù)器。
命令執(zhí)行: 在遠(yuǎn)程服務(wù)器上執(zhí)行命令。
文件傳輸: 在本地和遠(yuǎn)程服務(wù)器之間傳輸文件。
交會時會話: 類比xshell,當(dāng)代碼執(zhí)行后,如同在操作真實的xshell一樣二、重要知識點(diǎn)
1.安裝ssh庫
代碼如下(示例):
go get golang.org/x/crypto/ssh
2.ssh庫重要知識牢記
結(jié)合演示代碼一起更好理解
如下(示例):
1、client 對象(SSH 客戶端)在整個程序中只創(chuàng)建一次 2、可以通過 client.NewSession() 多次創(chuàng)建多個 session 對象.每個 session 是一個獨(dú)立的會話,每次執(zhí)行命令時都會創(chuàng)建一個新的會話 3、每次 session.Run() 或 session.Start() 執(zhí)行命令時,都會用新的會話來執(zhí)行不同的命令 這些會話共享底層的 SSH 連接,但是它們獨(dú)立執(zhí)行命令 4、當(dāng)某個會話的命令執(zhí)行完畢,必須調(diào)用session.Close() 釋放相關(guān)資源。 5、切記不能在同一個 session 上并行執(zhí)行多個命令。如果需要并行執(zhí)行多個命令,應(yīng)該創(chuàng)建多個 session
演示代碼(示例):
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"log"
)
func main() {
// SSH 配置
config := &ssh.ClientConfig{
User: "root", // 替換為遠(yuǎn)程服務(wù)器的用戶名
Auth: []ssh.AuthMethod{
ssh.Password("1"), // 替換為遠(yuǎn)程服務(wù)器密碼
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主機(jī)密鑰驗證
}
// 連接遠(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)建第一個會話
session1, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session 1: %v", err)
}
defer session1.Close()
// 執(zhí)行第一個命令
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)
}
// 演示在第一個會話中執(zhí)行第二個命令看是否能成功
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)建第二個會話
session2, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session 2: %v", err)
}
defer session2.Close()
// 執(zhí)行第二個命令
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)建第三個會話
session3, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session 3: %v", err)
}
defer session3.Close()
// 執(zhí)行第三個命令
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í)行這段代碼,返回如下所示,在同一個會話下并行的運(yùn)行兩條命令,發(fā)現(xiàn)運(yùn)行失敗

當(dāng)將1-2這段代碼注釋掉后,再次運(yùn)行代碼可以成功運(yùn)行,跟上述的描述一致


三、模擬連接遠(yuǎn)程服務(wù)器并執(zhí)行命令
演示怎么在golang中使用SSH庫連接服務(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ù)器,并返回一個ssh客戶端實例,
/*
返回值類型:
*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()
// 使用客戶端創(chuàng)建一個ssh會話
session, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Close()
// 在ssh會話中執(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)庫下執(zhí)行命令的幾種方式對比
| 方法 | 功能描述 | 阻塞/非阻塞 | 輸出捕獲 | 使用場景 |
|---|---|---|---|---|
| cmd:=exec.Command(“xx”,“x”) err:=cmd.Run() | 執(zhí)行本地命令并等待命令完成,返回錯誤 | 阻塞 | 不捕獲輸出(需用 Output/CombinedOutput 捕獲) | 本地命令執(zhí)行,等待命令完成 |
err:=newsession.Run("xxx") | 執(zhí)行遠(yuǎn)程命令并等待命令完成,返回錯誤 | 阻塞 | 不捕獲輸出(需手動捕獲) | 遠(yuǎn)程 SSH 命令執(zhí)行,等待完成 |
| cmd:=exec.Command(“xx”,“xx”) cmd.Start() | 啟動本地命令異步執(zhí)行,不等待命令完成 | 非阻塞,如果要阻塞,使用exec.Command().Wait()實現(xiàn) | 可通過 Stdout、Stderr 獲取輸出 | 本地命令異步執(zhí)行,非阻塞 |
err:=newsession.Start("xx") | 啟動遠(yuǎn)程命令異步執(zhí)行,不等待命令完成 | 非阻塞,適用于需要啟動后臺進(jìn)程的場景,如果要阻塞使用,newsession.Wait()實現(xiàn) | 可通過 Stdout、Stderr 獲取輸出 | 遠(yuǎn)程命令異步執(zhí)行,非阻塞 |
| cmd:=exec.Command(“xx”,“x”) out,err:=cmd.CombinedOutput() | 執(zhí)行本地命令并捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的合并輸出 | 阻塞 | 捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的合并輸出 | 本地命令執(zhí)行,捕獲所有輸出 |
out,err:=newsession.CombinedOutput("xx") | 執(zhí)行遠(yuǎn)程命令并捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的合并輸出 | 阻塞 | 捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤的合并輸出 | 遠(yuǎn)程命令執(zhí)行,捕獲所有輸出 |
五、SSH庫下三種執(zhí)行命令方式演示
5.1. session.CombinedOutput()示例
連接192.168.56.160服務(wù)器,并執(zhí)行l(wèi)s /var/log/命令查看目錄下的文件
注意事項:
1、CombinedOutput()函數(shù)剖析
func (s *ssh.Session) CombinedOutput(cmd string) ([]byte, error)
接收參數(shù)類型 string
返回值類型[]byte,error
將[]byte轉(zhuǎn)換為string類型輸出的結(jié)果為命令的執(zhí)行結(jié)果
2、在一個session會話中執(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ù)器,并返回一個ssh客戶端實例
client, err := ssh.Dial("tcp", "192.168.56.160:22", config)
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer client.Close()
// 使用客戶端創(chuàng)建一個ssh會話
session, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Close()
// 在ssh會話中執(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()示例
注意事項: session.Run(cmd string )error func (s *ssh.Session) Run(cmd string) error 接收參數(shù)類型 string 返回類型 error <如果要想獲取到執(zhí)行的結(jié)果和錯誤,即區(qū)分標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤,則使用下方的方法>
package main
import (
"bytes"
"fmt"
"log"
"golang.org/x/crypto/ssh"
)
// setupSSHClient 配置并返回一個SSH客戶端
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),僅用于示例。在實際應(yīng)用中,你應(yīng)該驗證主機(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)建一個緩沖區(qū)來捕獲命令的輸出
var outputBuf bytes.Buffer
// 將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤都重定向到同一個緩沖區(qū)
newsession.Stdout = &outputBuf
newsession.Stderr = &outputBuf
err := newsession.Run("ls /var/log/audit/")
if err != nil {
// 輸出執(zhí)行命令時的錯誤
fmt.Printf("Error executing command: %v\n", err)
}
// 打印命令的輸出(包括標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤)
fmt.Printf("Command output:\n%s\n", outputBuf.String())
}
}
5.3. session.Start()、session.Wait()示例
注意事項:
func (s *ssh.Session) Start(cmd string) error
接收參數(shù)類型 string
返回類型 error
如果要想獲取到執(zhí)行的結(jié)果和錯誤,即區(qū)分標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤,則使用下方的方法
func (s *ssh.Session) Wait() error
返回類型 error
等待
session.Start() 單獨(dú)使用時,命令會在后臺執(zhí)行,程序不會等待命令的完成,立即繼續(xù)執(zhí)行后續(xù)代碼。
session.Start() 和 session.Wait() 一起使用時,程序會在 Wait() 處等待命令執(zhí)行完成,之后才會繼續(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ù)器的用戶名
Auth: []ssh.AuthMethod{
ssh.Password("1"), // 替換為遠(yuǎn)程服務(wù)器密碼
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主機(jī)密鑰驗證
}
// 連接遠(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)建會話
session, err := client.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Close()
// 示例 1:使用 session.Start() 啟動命令,但不等待
fmt.Println("=== 示例 1: 使用 session.Start() 啟動命令,不等待 ===")
err = session.Start("sleep 5") // 啟動一個后臺命令
if err != nil {
log.Fatalf("Failed to start command: %v", err)
}
// 程序不會等待 sleep 5 執(zhí)行完成,立即繼續(xù)執(zhí)行下一行
fmt.Println("命令已啟動,程序繼續(xù)執(zhí)行,不等待命令結(jié)束")
// 等待一段時間,觀察命令是否執(zhí)行完
time.Sleep(2 * time.Second)
fmt.Println("程序在等待2秒后繼續(xù)執(zhí)行。")
// 示例 2:使用 session.Start() 啟動命令,并等待命令執(zhí)行完畢
// 創(chuàng)建新的會話用于第二個命令
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() 啟動命令,并調(diào)用 session.Wait() 等待 ===")
err = session2.Start("sleep 5") // 啟動一個后臺命令
if err != nil {
log.Fatalf("Failed to start second command: %v", err)
}
// 程序會在這里等待命令執(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)錯誤的方法:
StdoutPipe / StderrPipe 和 session.Stdout / session.Stderr之間的區(qū)別
6.1. 使用 StdoutPipe 和 StderrPipe捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤
重要代碼示例
// 獲取 標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤
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()` 返回一個 `io.Reader`,可以用來讀取遠(yuǎn)程命令的標(biāo)準(zhǔn)輸出(stdout)和標(biāo)準(zhǔn)錯誤輸出(stderr) - 可以通過從這些管道中讀取數(shù)據(jù)來獲取命令的輸出,通常會使用協(xié)程來異步讀取這些管道中的數(shù)據(jù) 2. 工作原理: - 首先通過 `session.StdoutPipe()` 和 `session.StderrPipe()` 獲取輸出的管道(`io.Reader`) - 然后在程序中手動讀取這些管道的內(nèi)容,通常通過 `io.Copy` 或者 `bufio.Reader` 來處理流。 - 這種方式適用于需要處理較大輸出或需要實時讀取命令輸出的場景。 3. 優(yōu)點(diǎn): - 可以實時讀取輸出,因為管道是持續(xù)開放的,適合需要處理大量數(shù)據(jù)或逐行輸出的情況。 - 可以分別處理標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤,提供更多靈活性。 4. 缺點(diǎn): - 需要異步讀取標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤,可能需要更多的代碼來確保并發(fā)處理和同步。 - 適用于需要實時處理輸出的場景,不適合簡單的命令輸出捕獲。
6.2. 使用 重定向
session.Stdout 和 session.Stderr 捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤
重要代碼示例
....
// 創(chuàng)建一個緩沖區(qū)來捕獲命令的輸出
var outputBuf bytes.Buffer
// 將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤都重定向到同一個緩沖區(qū)
session.Stdout = &outputBuf
session.Stderr = &outputBuf
err := newsession.Run("ls /var/log/audit/")
if err != nil {
// 輸出執(zhí)行命令時的錯誤
fmt.Printf("Error executing command: %v\n", err)
}
// 打印命令的輸出(包括標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤)
fmt.Printf("Command output:\n%s\n", outputBuf.String())
...解釋
1. 使用 `newsession.Stdout` 和 `newsession.Stderr`: - `session.Stdout` 和 `session.Stderr` 分別是 `io.Writer` 類型,允許將命令的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤直接寫入一個緩沖區(qū)(如 `bytes.Buffer`)。 - 可以通過 `outputBuf.String()` 獲取完整的命令輸出。這里,`Stdout` 和 `Stderr` 都被重定向到同一個 `bytes.Buffer`, - 這樣就能捕獲命令的所有輸出(無論是標(biāo)準(zhǔn)輸出還是標(biāo)準(zhǔn)錯誤)。 2. 工作原理: - `session.Run()` 會直接執(zhí)行命令并把標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤都寫入到指定的緩沖區(qū)。 - 不需要異步讀取輸出,命令執(zhí)行完成后,只需要讀取 `outputBuf` 即可獲取所有輸出。 3. 優(yōu)點(diǎn): - 代碼簡單,易于實現(xiàn),適合捕獲簡單的命令輸出。 - 不需要顯式地管理異步讀取標(biāo)準(zhǔn)輸出和錯誤流,適用于不需要實時處理輸出的場景。 - 適合于簡單的任務(wù)(例如調(diào)試、輸出日志等)并且輸出數(shù)據(jù)量較小的情況。 4. 缺點(diǎn): - 如果命令輸出量大或者需要實時處理輸出,可能會遇到緩沖區(qū)的限制或延遲。 - 不能實時讀取輸出,必須等命令執(zhí)行完畢才能獲取所有輸出。
6.3.兩種方式的區(qū)別
1. 實時性: - `StdoutPipe` 和 `StderrPipe`: 適合實時讀取標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤??梢栽诿顖?zhí)行的過程中動態(tài)處理輸出數(shù)據(jù)。 - `Stdout` 和 `Stderr`: 適合捕獲命令執(zhí)行后的完整輸出,并不實時讀取。如果需要完整的命令輸出,一次性獲取比較簡單。 2. 使用場景: - `StdoutPipe` 和 `StderrPipe`: 適合輸出較大、需要流式處理的場景,比如你需要逐行讀取或?qū)崟r處理命令輸出的場景。 - `Stdout` 和 `Stderr`: 適合捕獲命令的完整輸出并一次性處理,代碼簡單,適合小規(guī)模的輸出捕獲。 3. 復(fù)雜性: - `StdoutPipe` 和 `StderrPipe`: 稍微復(fù)雜,因為需要處理并發(fā)讀取輸出流,可能涉及協(xié)程。 - `Stdout` 和 `Stderr`: 簡單易懂,適合不需要實時讀取輸出的情況。 根據(jù)實際需求,可以選擇適合的方式: 如果需要并發(fā)處理或?qū)崟r處理輸出流,使用 `StdoutPipe` 和 `StderrPipe` 如果需要一次性獲取完整輸出,使用 `Stdout` 和 `Stderr` 會更加簡潔。
七、示例: 連接到多臺服務(wù)器并執(zhí)行多個命令返回命令執(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)建一個新的 SSH 會話
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
}
// 啟動命令
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
}
// 使用鎖來確保對共享資源(如輸出的打?。┦谴械?
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 客戶端配置,使用密碼認(rèn)證
sshConfig := &ssh.ClientConfig{
User: "root", // SSH 用戶名
Auth: []ssh.AuthMethod{
ssh.Password("1"), // 密碼認(rèn)證
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 注意:生產(chǎn)環(huán)境中不建議使用此選項
}
// 創(chuàng)建一個管道用于接收結(jié)果
resultChan := make(chan string, len(ips)*3) // 每臺機(jī)器執(zhí)行多個命令,調(diào)整管道容量
var wg sync.WaitGroup
var mu sync.Mutex // 創(chuàng)建鎖
// 遍歷 IP 地址,并為每個 IP 地址啟動一個 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()
// 對每臺機(jī)器執(zhí)行多個命令
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)
}
}
涉及到的知識點(diǎn): 1、管道 2、互斥鎖 3、goroutine并發(fā) 4、SSH 5、session.Start/Wait 6、分開捕獲標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤 7、按行讀取文件內(nèi)容 上述代碼示例演示了如何在多臺機(jī)器上并發(fā)執(zhí)行多個命令,并使用 sync.Mutex 來保護(hù)共享資源(如管道)的訪問 具體流程: 1、從文件中按行讀取IP并保存到切片ips中 2、設(shè)置ssh配置,從管道中讀取IP,將每個服務(wù)器連接和每個要執(zhí)行的命令都放在一個 goroutine中。 主程序繼續(xù)啟動新的 goroutine 執(zhí)行任務(wù),而不會因為某一臺服務(wù)器的命令執(zhí)行而導(dǎo)致整個程序阻塞 3、將連接信息和捕獲的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤信息都寫入到管道中 4、當(dāng)服務(wù)器連接成功后,調(diào)用執(zhí)行命令函數(shù)executeCommand,再該代碼中的鎖用于保護(hù)共享資源(resultChan)的訪問 因為如果多個 goroutine 同時向通道發(fā)送數(shù)據(jù)(比如日志輸出) 沒有鎖會導(dǎo)致輸出混亂(多個 goroutine 的日志可能會交錯,難以看清) 使用 sync.Mutex 來確保每次只有一個 goroutine 向通道發(fā)送數(shù)據(jù),從而保證輸出日志的順序和一致性 保證了多個 goroutine 在寫入 resultChan 時不會互相干擾,避免了并發(fā)寫入導(dǎo)致的數(shù)據(jù)不一致或錯亂 5、當(dāng)所有遠(yuǎn)程機(jī)器的命令執(zhí)行完成后,關(guān)閉會話、關(guān)閉通道,最終再打印出通道中所有的日志信息
總結(jié)
以上就是SSH標(biāo)準(zhǔn)庫自己整理的知識,故不積跬步,無以至千里;不積小流,無以成江海,慢慢整理golang中運(yùn)維可以使用到的相關(guān)庫,向運(yùn)維逐漸靠攏
到此這篇關(guān)于golang標(biāo)準(zhǔn)庫SSH操作示例的文章就介紹到這了,更多相關(guān)golang標(biāo)準(zhǔn)庫SSH內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?defer?return?panic?執(zhí)行順序示例詳解
這篇文章主要介紹了go?defer?return?panic?執(zhí)行順序,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01
Go語言MySQLCURD數(shù)據(jù)庫操作示例詳解
這篇文章主要為大家介紹了Go語言MySQLCURD數(shù)據(jù)庫操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
golang結(jié)構(gòu)化日志slog的用法簡介
日志是任何軟件的重要組成部分,Go?提供了一個內(nèi)置日志包(slog),在本文中,小編將簡單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下2023-09-09
使用Go語言創(chuàng)建error的幾種方式小結(jié)
Go語言函數(shù)(或方法)是支持多個返回值的,因此在Go語言的編程哲學(xué)中,函數(shù)的返回值的最后一個通常都是error類型,所以本文給大家介紹了使用Go語言創(chuàng)建error的幾種方式小結(jié),文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01

