在Windows下,有着许多优秀的SSH客户端,在Linux上,选择就没有那么广泛了

使用ssh连接服务器是每一个软件开发者常见的操作,无论是免费的MobaXterm还是付费的termius都算得上是非常好用,尤其是termius,作为商业工具兼顾了易用与美观

termius收费较高,免费的版本缺失不少功能,与免费的MobaXterm没有太大差别

后来发现直接使用ssh也非常好用,如在$HOME/.ssh/config添加如下配置

Host company-develop
  HostName 192.168.122.4
  Port 22
  User chancel
  IdentityFile /home/chancel/.ssh/id_rsa

...

然后在命令行输入ssh+tab按键,可以看到智能补全

~❯ ssh company-
company-develop  ...

使用多了,发现这样也非常便捷

1. config

$HOME/.ssh/config文件是SSH客户端的配置文件,用于设置和管理SSH连接的参数和选项,它可以包含多个主机配置块,每个主机配置块定义了与特定主机的详细连接参数

配置文件中的每个主机配置块由以下几个常见部分组成:

  1. Host:指定主机的名称或模式匹配规则(例如,匹配通配符或正则表达式)
  2. HostName:指定要连接的主机的名称或IP地址3. User:指定要使用的用户名4. Port:指定连接的端口号5. IdentityFile:指定用于身份验证的私钥文件路径6. ForwardAgent:指定是否在连接中转发本地SSH代理7. ProxyCommand:指定连接中使用的代理命令 通过修改$HOME/.ssh/config文件,可以方便地设置和管理SSH连接的参数,不用每次连接时都手动输入命令行选项

此外,配置文件还可以提供更高级的功能,如通过别名快速连接主机、设置跳板主机、配置连接超时等,这些参数可以参考man ssh_config获得

1.1. 实践

编辑~/.ssh/config 文件

Host chancel-pc # 别名,用于连接时驶入
    HostName 192.168.10.1 # 连接IP
    User ycs # 用户名
    Port 10086 # SSH 端口
    ServerAliveInterval 60 # 每60秒发送一次心跳避免SSH连接中断
    IdentityFile .ssh/id_rsa # 本地证书

然后授权

chmod 600 ~/.ssh/config

测试连接

ssh chancel-pc

2. go-ssh

2.1. 列表管理

如果config文件中配置的服务器多起来还是非常难管理的,这个时候图形界面的SSH客户端会有优势,能够列表展示所有服务器信息

这个时候我们可以利用一些第三方插件来实现如ssh-config-managersshmenusshrc

2.2. 代码实现

考虑到这个需求并不复杂,且ssh的端口、证书、密码属于敏感信息,可以自己实现一个脚本来展现列表形式

这里用golang来实现,借助golang优秀的打包机制,可以写一次代码在多个平台上使用

package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// Function to parse SSH configuration file and return a slice of maps containing the configuration details
func parseSSHConfig(configPath string) ([]map[string]string, error) {
	// Open the SSH configuration file
	configFile, err := os.Open(configPath)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	defer configFile.Close()

	// Initialize variables to store the configuration data
	configDicts := []map[string]string{} // Slice of maps to store multiple configurations
	currentDict := map[string]string{} // Map to store the current configuration

	// Create a scanner to read the configuration file line by line
	scanner := bufio.NewScanner(configFile)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text()) // Remove leading and trailing whitespaces

		// Skip empty lines and lines starting with "#"
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// If line starts with "Host ", it indicates a new configuration block
		if strings.HasPrefix(line, "Host ") {
			// If there is any existing configuration, add it to the configDicts slice
			if len(currentDict) > 0 {
				configDicts = append(configDicts, currentDict)
			}

			// Create a new map for the current configuration block
			currentDict = map[string]string{}
			currentDict["Host"] = strings.TrimSpace(strings.TrimPrefix(line, "Host "))
		} else {
			// Split the line into key-value pairs
			parts := strings.SplitN(line, " ", 2)
			if len(parts) == 2 {
				key := strings.TrimSpace(parts[0])
				value := strings.TrimSpace(parts[1])

				// Store the key-value pair in the currentDict map
				currentDict[key] = value
			}
		}
	}

	// Add the last configuration block to the configDicts slice
	if len(currentDict) > 0 {
		configDicts = append(configDicts, currentDict)
	}

	// Return the slice of maps containing the configuration details and no error
	return configDicts, nil
}

// Function to print the SSH configuration
func printSSHConfig(configs []map[string]string) {
	// Printing header
	fmt.Println("************************ Hi, Welcome to use Go-SSH Tool *****************************")
	fmt.Println()
	fmt.Println("+-----+------------------------------+-------------------------+------------------------------------------+")
	fmt.Println("| id  | Host                         | username                | address                                  |")
	fmt.Println("+-----+------------------------------+-------------------------+------------------------------------------+")

	// Printing each SSH configuration
	for i, config := range configs {
		fmt.Printf("| %-3d | %-28s | %-23s | %-40s |\n", i+1, config["Host"], config["User"], config["HostName"])
	}

	fmt.Println("+-----+------------------------------+-------------------------+------------------------------------------+")
	fmt.Println()
	fmt.Println("Tips: Press a number between 1 and", len(configs)-1, "to select the host to connect, or \"q\" to quit.")
	fmt.Println()
}

func main() {
	// Parsing command line arguments
	configPath := flag.String("c", "$HOME/.ssh/config", "Path to SSH config file")
	flag.Parse()

	// Expanding environment variables in the config path
	expandedPath := os.ExpandEnv(*configPath)

	// Parsing the SSH config file
	configs, err := parseSSHConfig(expandedPath)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Printing the SSH configuration
	printSSHConfig(configs)

	// Creating a scanner to read user input
	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Print("# ")
		scanner.Scan()
		input := scanner.Text()

		// Exiting the program if user enters "q"
		if input == "q" {
			return
		}

		id := -1
		// Parsing the user input to get the selected SSH configuration ID
		fmt.Sscanf(input, "%d", &id)
		if id >= 1 && id <= len(configs) {
			// Getting the selected SSH configuration
			sshConfig := configs[id-1]

			// Creating arguments for the "ssh" command
			var args []string
			for key, value := range sshConfig {
				// Skipping "Host", "User", and "HostName" keys
				if key == "Host" || key == "User" || key == "HostName" {
					continue
				}
				arg := fmt.Sprintf("-o %s=%s", key, value)
				args = append(args, arg)
			}

			// Appending username and host name to the command arguments
			cmdArgs := append(args, fmt.Sprintf("%s@%s", sshConfig["User"], sshConfig["HostName"]))

			// Creating the "ssh" command
			sshCmd := exec.Command("ssh", cmdArgs...)
			sshCmd.Stdout = os.Stdout
			sshCmd.Stdin = os.Stdin
			sshCmd.Stderr = os.Stderr

			// Running the "ssh" command
			err := sshCmd.Run()
			if err != nil {
				fmt.Println("Error:", err)
				os.Exit(0)
			}
		} else {
			fmt.Println("Error: Invalid input")
		}

		// Printing the SSH configuration after each iteration
		printSSHConfig(configs)
	}
}

代码仓库:github仓库 - chancelyg/go-ssh

上面的代码请自行审阅,这个工具可以实现$HOME/.ssh/config中的ssh服务器以列表的形式呈现出来

你可以使用一个数字选择要连接的主机,然后会自动使用SSH连接到该主机

2.3. 使用效果

如下

❯ ./go-ssh
************************ Hi, Welcome to use Go-SSH Tool *****************************

+-----+------------------------------+-------------------------+------------------------------------------+
| id  | Host                         | username                | address                                  |
+-----+------------------------------+-------------------------+------------------------------------------+
| 1   | 192.168.122.4                | chancel                 | 192.168.122.4                            |
| 2   | 192.168.4.15                 | chancel                 | 192.168.4.15                             |
| 3   | 192.168.11.2                 | chancel                 | 192.168.11.2                             |
| 4   | 192.168.11.3                 | chancel                 | 192.168.11.3                             |
| 5   | 192.168.11.12                | chancel                 | 192.168.11.12                            |
| 6   | 192.168.4.10                 | chancel                 | 192.168.4.10                             |
+-----+------------------------------+-------------------------+------------------------------------------+

Tips: Press a number between 1 and 5 to select the host to connect, or "q" to quit.

# 

3. 尾声

灵感