欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang標(biāo)準(zhǔn)庫(kù)SSH操作示例詳解

 更新時(shí)間:2024年12月27日 15:10:55   作者:迷茫運(yùn)維路  
文章介紹了如何使用Golang的crypto/ssh庫(kù)實(shí)現(xiàn)SSH客戶(hù)端功能,包括連接遠(yuǎn)程服務(wù)器、執(zhí)行命令、捕獲輸出以及與os/exec標(biāo)準(zhǔn)庫(kù)的對(duì)比,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧

前言

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 / StderrPipesession.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.Stdoutsession.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í)行順序示例詳解

    這篇文章主要介紹了go?defer?return?panic?執(zhí)行順序,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-01-01
  • Go語(yǔ)言快速入門(mén)圖文教程

    Go語(yǔ)言快速入門(mén)圖文教程

    Go是 Goolge 開(kāi)發(fā)的一種靜態(tài)型、編譯型、并發(fā)型,并具有垃圾回收功能的語(yǔ)言,Go 語(yǔ)言上手非常容易,它的風(fēng)格類(lèi)似于 C 語(yǔ)言,Go 語(yǔ)言號(hào)稱(chēng)是互聯(lián)網(wǎng)時(shí)代的 C 語(yǔ)言,那么它到底有多火呢,一起看看吧
    2021-05-05
  • Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解

    Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言MySQLCURD數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 一文了解golang 占位符

    一文了解golang 占位符

    本文主要介紹了一文了解golang 占位符,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang beyla采集trace程序原理源碼解析

    golang beyla采集trace程序原理源碼解析

    beyla支持通過(guò)ebpf,無(wú)侵入的、自動(dòng)采集應(yīng)用程序的trace信息,本文以golang的nethttp為例,講述beyla對(duì)trace的采集的實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2024-02-02
  • golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介

    golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介

    日志是任何軟件的重要組成部分,Go?提供了一個(gè)內(nèi)置日志包(slog),在本文中,小編將簡(jiǎn)單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下
    2023-09-09
  • Go語(yǔ)言的管道Channel用法實(shí)例

    Go語(yǔ)言的管道Channel用法實(shí)例

    這篇文章主要介紹了Go語(yǔ)言的管道Channel用法,實(shí)例分析了Go語(yǔ)言中管道的原理與使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • go語(yǔ)言接口的定義和實(shí)現(xiàn)簡(jiǎn)單分享

    go語(yǔ)言接口的定義和實(shí)現(xiàn)簡(jiǎn)單分享

    這篇文章主要介紹了go語(yǔ)言接口的定義和實(shí)現(xiàn)簡(jiǎn)單分享的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • go語(yǔ)言 全局變量和局部變量實(shí)例

    go語(yǔ)言 全局變量和局部變量實(shí)例

    這篇文章主要介紹了go語(yǔ)言 全局變量和局部變量實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 使用Go語(yǔ)言創(chuàng)建error的幾種方式小結(jié)

    使用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

最新評(píng)論