【bash】2、手把手实现一个 bash shell:多个机器批量执行 shell 命令,支持 ip 补全

一、需求:多台机器批量远程执行 shell 命令

这个脚本可以执行任何命令,用途是广泛的,例如执行 ls 查看目录,或执行 df 查看磁盘占用率

1.1 业务需求拆解为脚本需求

首先可以写 shell 批量获取各机器的磁盘占用率,通过在各机器执行 df -h | head 实现

希望的使用示例为:

mgr.sh 2.99 2.100 df -h | head

1.2 帮助函数:使用说明文档

首先写 print_help()

 # print_help
 print_help() {
 cat << EOF
 $0
     ssh IP1 IP2 ... [cmd]            -- ssh 连接 IP,执行 cmd
     fullip SUFFIX_IP                 -- 获取完整的 IP 地址
 EOF
 exit
 }

1.3 main 函数框架

然后写 main 函数

# main
 case $1 in
     ssh)
         shift
         sshp "$@"
         ;;
     fullip)
         shift
         fullip "$@"
         ;;
     *)
         print_help
         ;;
 esac

二、功能:单机 sshp 执行

2.1 fullip 函数:实现 ip 补全

2.1.1 参数说明

然后实现 fullip 函数,先写改函数的参数说明:

 # fullip
 # 根据 ip 后缀,补全完整的 ip 地址
 # 参数$1 为 ip 地址的后缀
 fullip() {

 }

2.1.2 定义全局变量

然后在文件开头,定义全局变量 ip_prefix=192.168

# 全局信息
# ip 前缀
PRE_IP=192.168

2.1.3 实现:正则匹配、if 分支

然后实现 fullip 函数

 # 全局信息
 # ip 前缀
 PRE_IP=192.168

 # fullip
 # 根据 ip 后缀,补全完整的 ip 地址
 # 参数$1 为 ip 地址的后缀
 fullip() {
     [ "x$1" == "x" ] && echo "Error: 请输入 IP 后缀" && exit 10
     # 已经是完整的 ip 地址
     if [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
         echo $1
         return
     # 只有 2 位的ip 后缀
     elif
         [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
         # 补全完整的 ip 地址
         echo $PRE_IP.$1
         return
     else
         echo -e "Error: 请输入正确的 IP 后缀"
         exit 101
     fi
 }

2.1.4 测试

然后测试脚本, 一切正常:

# 先执行脚本,不输入参数
./mgr.sh
./mgr.sh
    ssh IP1 IP2 ... [cmd]            -- ssh 连接 IP,执行 cmd
    fullip SUFFIX_IP                 -- 获取完整的 IP 地址
    
# 再执行 fullip,输入全量 ip
./mgr.sh fullip 192.168.2.1
192.168.2.1

# 再执行 fullip,输入ip 后缀
./mgr.sh fullip 2.99
192.168.2.99

2.2 sshp 函数

然后,写 sshp 函数,通过 sshpass 远程执行命令

2.2.1 参数说明

首先写参数说明

# sshp: 通过 sshpass 执行远程命令
# $1 是补全的 ip
# ssh 协议的用户名和密码,是写死的全局变量
# $@ 是ssh 需执行的 cmd

然后补全函数声明, 其实 shell 函数都是形如 f() {} 的

# sshp: 通过 sshpass 执行远程命令
# $参数 $1 为 ip 地址, 和 shell 的 $1 是一样的,可能是完整的 ip,也可能是 ip 的后缀
# ssh 协议的用户名和密码,是写死的全局变量
# $@ 是ssh 需执行的 cmd
sshp() {
    
}

2.2.2 定义全局变量

然后,定义全局变量,即 ssh 的用户名和密码。

这取决于需求,如果所操作的机器的用户名和密码都是相同的,则可以写死为全局变量。否则可以在脚本参数中输入。

USER=ubuntu
PASSWORD=ubuntu

2.2.3 实现:利用 sshpass 传参

 sshp() {
     sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no "$USER"@$1 "$@"
 }

注意,sshpass 是 main 函数的入口,所以其输入的参数 $1 就是整个 shell 的 参数 $1。

而 $1 虽然是 ip,但可能是完整的 ip 或者 仅 ip 后缀。

所以需要在 sshp() 做两步工作:

  1. 内调用 fullip() 实现 ip 的补全
  2. 将补全的 ip 传参给 sshpass

因此改写为如下:

 # sshp
 # 利用 sshpass 连接到指定的 ip 地址,并执行命令
 # 参数 $1 为 ip 地址, 和 shell 的 $1 是一样的,可能是完整的 ip,也可能是 ip 的后#缀
 sshp() {
     local remote=$(fullip $1)
     echo -e "remote is: $remote"
     # 注意解析完 $1 后,则需要 shift,以便解析后续参数
     shift
     sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no "$USER"@${remote} "$@"
 }

2.2.4 测试

# 打印帮助信息
$ ./mgr.sh
./mgr.sh
    ssh IP1 IP2 ... [cmd]            -- ssh 连接 IP,执行 cmd
    fullip SUFFIX_IP                 -- 获取完整的 IP 地址

# 远程 ssh 执行一个命令
$ ./mgr.sh ssh 2.99 ls
remote is: 192.168.299
a.txt
b.json

三、功能:多机批量 batch_sshp 执行

3.1 batch_sshp 函数:多机 sshp 执行

需求拆解:

多机 sshp 批量执行,因为要多个机器执行,所以需要填多个 ip,例如以空格分隔

因此 ips 和 cmd 之间要有明确的分隔符,例如 cmd

3.1.1 参数说明

./mgr.sh batch_ssh < ip | ip_suffix …> cmd

# 首先解析 ip 列表
# 其次解析 shell-cmds(通过 cmd 关键字)
bahch_ssh() {
    
}

思路:利用 awk 分隔参数

首先,main 解析 $1,如果是 “batch_ssh”,则执行 batch_ssh() 并将参数 shift

然后,用 awk 按 cmd 关键字,拆分参数为 ips 和 shell-cmd,存储到两个 local 变量里

其次,for ip in ips 遍历得到 ip 列表,每次循环都执行一次 sshp 函数

而,shell-cmd 变量则无需解析,直接在 sshp 函数使用即可

所以,完善的函数声明如下:

# batch_ssh
# 对多个机器,批量执行命令
# 根据 "cmd" 关键字,将命令分成两部分,前一部分是 ips 地址数组,后一部分是命令
# 用 for 遍历 ips 数组,对每个 ip 执行命令
batch_ssh() {

}

3.1.2 实现

3.1.2.1 利用 awk 分隔一个参数
# batch_ssh
# 对多个机器,批量执行命令
# 根据 "cmd" 关键字,将命令分成两部分,前一部分是 ips 地址数组,后一部分是命令
# 用 for 遍历 ips 数组,对每个 ip 执行命令
 batch_ssh() {
     local ips=$(echo "$@" | awk -F 'cmd' '{print $1}')
     echo -e "ips: ${ips}"
     echo -e "ips: ${ips[@]}"
 }

测试:

./mgr.sh batch_ssh 2.99 2.100
ips: 2.99 2.100
ips: 2.99 2.100
3.1.2.2 利用 awk 分隔多个参数
 batch_ssh() {
     local ips=$(echo "$@" | awk -F 'cmd' '{print $1}')
     local shellcmds=$(echo "$@" | awk -F 'cmd' '{print $2}')
     echo -e "ips: ${ips}"
     echo -e "shellcmds: ${shellcmds}"
 }

测试:

./mgr.sh batch_ssh 2.99 2.100 cmd ls -lrt
ips: 2.99 2.100
shellcmds:  ls -lrt
3.1.2.3 批量执行脚本
 batch_ssh() {
     local ips=$(echo "$@" | awk -F 'cmd' '{print $1}')
     local shellcmd=$(echo "$@" | awk -F 'cmd' '{print $2}')
     echo -e "ips: ${ips}"
     echo -e "shellcmds: ${shellcmd}"
     for ip in ${ips}; do
         local remote=$(fullip $ip)
         echo -e "\n-----开始执行: $remote-----"
         sshp $remote $shellcmd
     done
     echo -e "执行完毕"
 }

并补全帮助信息

 # print_help
 print_help() {
 cat << EOF
 $0
     ssh < IP | IPSUFFIX ...> <shellcmd>            -- ssh 连接 IP,执行 cmd
     batch_ssh < IP | IPSUFFIX ...> cmd <shellcmd>  -- 对多个机器,批量执行命令
     fullip SUFFIX_IP                               -- 获取完整的 IP 地址
 EOF
 exit
 }

3.1.3 测试、使用示例

这个脚本可以执行任何命令,用途是广泛的

3.1.3.1 执行 ls 查看目录
./mgr.sh batch_ssh 2.99 2.100 cmd ls -lrt
ips: 2.99 2.100
shellcmds:  ls -lrt

-----开始执行: 192.168.2.99-----
remote is: 192.168.2.99
a.txt
b.json

-----开始执行: 192.168.2.100-----
remote is: 192.168.2.100
c.json
d.ini

执行完毕
3.1.3.2 执行 df 查看磁盘占用率
mgr.sh batch_ssh 2.99 2.100 cmd df -h
ips: 2.99 2.100
shellcmds:  df -h

-----开始执行: 192.168.2.99-----
remote is: 192.168.2.99
Filesystem      Size  Used Avail Use% Mounted on
udev             16G     0   16G   0% /dev
tmpfs           3.2G  1.8M  3.2G   1% /run
/dev/vda2        49G   25G   22G  54% /

-----开始执行: 192.168.2.100-----
remote is: 192.168.2.100
Filesystem      Size  Used Avail Use% Mounted on
udev             48G     0   48G   0% /dev
tmpfs           9.5G  899M  8.6G  10% /run
/dev/sda1       117G   68G   44G  61% /

执行完毕

3.1.4 完整脚本

#!/bin/bash

# 全局信息
# ip 前缀
PRE_IP=192.168
USER=ubuntu
PASSWORD=ubuntu

# fullip
# 根据 ip 后缀,补全完整的 ip 地址
# 参数$1 为 ip 地址的后缀
fullip() {
    [ "x$1" == "x" ] && echo "Error: 请输入 IP 后缀" && exit 10
    # 已经是完整的 ip 地址
    if [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
        echo $1
        return
    # 只有 2 位的ip 后缀
    elif
        [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        # 补全完整的 ip 地址
        echo ${PRE_IP}.$1
        return
    else
        echo -e "Error: 请输入正确的 IP 后缀"
        exit 101
    fi
}

# sshp
# 利用 sshpass 连接到指定的 ip 地址,并执行命令
# 参数 $1 为 ip 地址, 和 shell 的 $1 是一样的,可能是完整的 ip,也可能是 ip 的后#缀
sshp() {
    local remote=$(fullip $1)
    echo -e "remote is: $remote"
    shift
    sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no "$USER"@${remote} "$@"
}

# batch_ssh
# 对多个机器,批量执行命令
# 根据 "cmd" 关键字,将命令分成两部分,前一部分是 ips 地址数组,后一部分是命令
# 用 for 遍历 ips 数组,对每个 ip 执行命令
batch_ssh() {
    local ips=$(echo "$@" | awk -F 'cmd' '{print $1}')
    local shellcmd=$(echo "$@" | awk -F 'cmd' '{print $2}')
    echo -e "ips: ${ips}"
    echo -e "shellcmds: ${shellcmd}"
    for ip in ${ips}; do
        local remote=$(fullip $ip)
        echo -e "\n-----开始执行: $remote-----"
        sshp $remote $shellcmd
    done
    echo -e "\n执行完毕"
}


# print_help
print_help() {
cat << EOF
$0
    ssh < IP | IPSUFFIX ...> <shellcmd>            -- ssh 连接 IP,执行 cmd
    batch_ssh < IP | IPSUFFIX ...> cmd <shellcmd>  -- 对多个机器,批量执行命令
    fullip SUFFIX_IP                               -- 获取完整的 IP 地址
EOF
exit
}

# main
case $1 in
    ssh)
        shift
        sshp "$@"
        ;;
    batch_ssh)
        shift
        batch_ssh "$@"
        ;;
    fullip)
        shift
        fullip "$@"
        ;;
    *)
        print_help
        ;;
esac

执行效果

$ ./mgr.sh
./mgr.sh
    ssh < IP | IPSUFFIX ...> <shellcmd>            -- ssh 连接 IP,执行 cmd
    batch_ssh < IP | IPSUFFIX ...> cmd <shellcmd>  -- 对多个机器,批量执行命令
    fullip SUFFIX_IP

环境配置:通常会在 vim 写 shell 脚本,可以用开源的 vimrc 配置 https://github.com/amix/vimrc 替我们解决高亮等美观问题,提升效率

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 要设计和实现一个模拟bashshell实现,首先需要一个主循环来接受用户输入的命令,并对其进行解析和执行。 在解析用户输入的命令时,首先需要判断命令类型。如果是cd命令,就需要对参数进行解析,在获取到目标路径后,使用chdir函数切换当前工作路径。如果是pwd命令,直接调用getcwd函数获取当前工作路径,并将其打印出来。 对于其他命令或程序的执行,可以使用fork函数创建一个新的进程,并使用exec系列函数在新的进程中执行指定的程序或命令。可以使用execvp函数,它可以根据环境变量PATH来查找可执行程序的路径。 路径解析可以使用相对路径和绝对路径。当用户输入的路径以'/'开头时,表示绝对路径,直接使用该路径。否则,表示相对路径,需要将当前工作路径与相对路径拼接起来。 此外,还可以考虑使用字符串分割函数或正则表达式来解析用户输入的命令,得到命令和参数的列表,便于后续操作。 在实现过程中,需要注意错误处理和异常情况的处理,例如不能找到目标路径或程序不存在等情况。 最后,可以添加一些额外的功能,如自动补全、历史命令记录、重定向等来提升用户体验。 总之,设计和实现一个模拟bashshell需要解析用户输入的命令类型,并根据具体的命令类型执行相应的操作。 ### 回答2: 为了设计和实现一个模拟bashshell实现,我们可以按照以下步骤进行: 1. 构建基础结构:创建一个无限循环,接受用户输入的shell界面。在每次循环中,打印提示符,并接收用户输入的命令。 2. 解析用户输入:根据空格分割用户输入的命令和参数,将其存储在相应的变量中。 3. 判断命令类型:通过判断用户输入的第一个参数,可以确定用户想要执行的具体命令类型。 4. 实现cd命令:如果用户输入的第一个参数是"cd",则需要调用系统的chdir函数,将进程的工作路径更改为用户输入的第二个参数(路径)。如果用户未提供路径,则默认将路径更改为当前用户的主目录。 5. 实现pwd命令:如果用户输入的命令是"pwd",则输出当前的工作路径。 6. 实现程序执行:如果用户输入的不是以上两个命令,则可以将用户输入的命令和参数传递给一个子进程,并使用exec函数执行该程序。 7. 路径解析:在执行程序之前,可以通过检查用户输入的命令是否包含"/"来判断是否为绝对路径。如果是相对路径,则需要将当前的工作路径和用户输入的路径合并,以获取完整的绝对路径。 需要注意的是,在实现过程中,可能需要处理各种错误情况,例如无效的命令或路径,权限问题等。此外,还可以考虑添加一些其他功能,如支持通配符、管道、重定向等。以上只是一个简单的示例,你可以根据具体需求进行适当的修改和扩展。 ### 回答3: 设计和实现一个模拟bashshell实现,满足以下要求: 1. 支持cd命令:当输入cd命令时,切换当前工作目录到用户指定的目录。如果用户没有指定目录,则返回当前工作目录。实现思路可以使用chdir函数来实现目录切换,并通过getcwd函数获取当前工作目录。 2. 支持pwd命令:当输入pwd命令时,打印当前工作目录。可以使用getcwd函数获取当前工作目录,并将其打印出来。 3. 支持程序执行:当输入其他命令时,假设为程序执行命令,可以使用fork函数创建子进程,并使用exec函数族中的任意一个函数来执行用户输入的命令。这里需要注意,子进程需要通过调用waitpid函数等待程序执行完毕,避免僵尸进程的产生。 4. 路径解析:在执行程序命令前,需要解析用户输入的命令,获取命令的路径和参数。可以使用strtok函数将用户输入的命令按照空格分割成不同的部分。首部分为命令路径,之后的部分为命令的参数。再利用exec函数族中的一个函数执行程序。 通过以上几个步骤,可以实现一个简单的模拟bashshell。在主函数中,使用一个while循环,不断接收用户输入的命令,对输入的命令进行解析和处理。要注意错误处理和异常情况的处理,比如命令不存在或者参数错误等情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆呆的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值