如何使用 ssh 连接创建具有多个变量的粒度 bash 脚本

How to create a granular bash script with multiple variables with ssh connections

我有以下脚本:

脚本:

#!/bin/bash
###########
printf "\n"
marker=$(printf "%0.s-" {1..60})
printf "|$marker|\n"
printf "|%-10s | %-13s | %-29s |\n" "Hostname" "RedHat Vesrion" "Perl Version"
printf "|$marker|\n"

remote_connect() {
   target_host=
   marker=$(printf "%0.s-" {1..60})
   rhelInfo=$(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no cat /etc/redhat-release| awk 'END{print }')
   perlInfo=$(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no "rpm -qa | grep -i mod_perl")
   if [[ $? -eq 0 ]]
   then
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "$rhelInfo" "$perlInfo"
   else
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "Unable to get the ssh connection"
fi
}  2>/dev/null
export -f remote_connect
< /home/zabbix/hostsList.txt  xargs -P30 -n1 -d'\n' bash -c 'remote_connect "$@"' --

在 运行 并行模式下,上面的脚本对我来说运行得很好。

脚本结果:

|------------------------------------------------------------|
|Hostname   | RedHat Vesrion | Perl Version                  |
|------------------------------------------------------------|
|foxnl41    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl84    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl42    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl63    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl10    | 6.7           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl55    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl95    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |
|foxnl85    | 6.9           | mod_perl-2.0.4-11.el6_5.x86_64 |

关注 ?

我有两个变量:rhelInfoperlInfo 来获取商店信息。但它使用对服务器的两次 ssh 调用来获取值。

我可以只有一个 SSH 调用来执行多个命令并设置两个变量吗?

Could I have only one SSH call to execute multiple commands and set both variables?

当然可以。你可以这样做:

# this looks too long - so a function
_ssh() {
  ssh -i /home/zabbix/.ssh/ssh_key \
      -o StrictHostKeyChecking=no \
      -o PasswordAuthentication=no \
      "root@$target_host" \
      "$@"
}
export -f _ssh

# the function to-be-executed on the remote
remotework() {
    rhelinfo=$(awk 'END{print }' /etc/redhat-release)
    perlinfo=$(rpm -qa | grep -i mod_perl)
    # output elements separated by byte 0x01
    printf "%s[=10=]1" "$rhelinfo" "$perlinfo"
}
export -f remotework

remote_connect() {
    # execute bash on the remote
    # with `remotework` function serialized
    # and execute the `remotework` function
    # properly `printf %q` quote everything for unquoting done by ssh+remote shell
    tmp=$( _ssh "$(printf "%q " bash -c "$(declare -f remotework); remotework")" )
    # split the output of ssh by byte 0x01
    {
       IFS= read -d $'\x01' -r rhelInfo &&
       IFS= read -d $'\x01' -r perlInfo
    } <<<"$tmp"
}

或它的类似变体。基本上 ssh 为您提供双向数据流 - 您可以在中间使用分隔符(“自定义协议”)传输任何内容,然后在该分隔符上拆分数据。 IE。问题不仅限于 ssh - bash 中的研究数据 serialization/deserialization。上面我选择了字节 0x01 作为分隔符 - 你可以使用具有唯一 uuid 的单独行,使用 base64 -w0 将数据转换为单行,或类似或使用其他格式。

remotework() {
    awk ... | base64 -w0
    echo ' '
    rpm ... | base64 -w0
    echo
}
...
   IFS=' ' read -r rhelInfo perlInfo <<<"$tmp"
   rhelInfo=$(<<<"$rhelInfo" base64 -d)
   perlInfo=$(<<<"$perlInfo" base64 -d)

您还可以使用 declare -p 序列化变量,然后 eval 它们 - 我认为这更危险,因此最好使用分隔符。

建议替换以下行:

   rhelInfo=$(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no cat /etc/redhat-release| awk 'END{print }')
   perlInfo=$(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no "rpm -qa | grep -i mod_perl")
   if [[ $? -eq 0 ]]; then
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "$rhelInfo" "$perlInfo"
   else
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "Unable to get the ssh connection"
   fi

具有以下几行:

   sshResponseArr=( $(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no "awk 'END{print}'  /etc/redhat-release; rpm -aq|grep -i mod_perl") ) 
   if [[ $? -eq 0 ]]; then
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "${sshResponseArr[6]}" "${sshResponseArr[8]}"
   else
     printf "|%-10s | %-13s | %-20s |\n" "$target_host" "Unable to get the ssh connection"
   fi

说明

首先,我在 ssh 请求中调用 multi-line 命令。将所有命令放在带有 " 的单个参数中(就像您所做的那样)。

真正的技巧是将 multi-line ssh 响应读入 bash 数组变量 sshResponseArr

默认情况下,数组由 个空格解析。

我假设(和你一样)数组中的单词定位在所有主机上都是一致的因此:

$rhelInfo${sshResponseArr[6]}

$perlInfo${sshResponseArr[8]}

sshResponseArr 的替代保守处理作为来自 ssh 命令的 2 行响应的数组:

 mapfile -t sshResponseLinesArr < <(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no "awk 'END{print}'  /etc/redhat-release; rpm -aq|grep -i mod_perl")
 lastLineInReleaseFile=${sshResponseLinesArr[0]}
 mod_perl_response=${sshResponseLinesArr[1]}

 

您可以 运行 在一个 ssh 运行 中执行这两个命令,然后解析结果,例如:

...
remote_data=($(
    ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no "
        rhelInfo=$(cat /etc/redhat-release | awk 'END{print $7}')
        perlInfo=$(rpm -qa | grep -i mod_perl)
        
        echo $rhelInfo $perlInfo  
"))

rhelInfo=${remote_data[0]}
perlInfo=${remote_data[1]}
...

说明: remote_data=( $(ssh ...) ) - 将创建一个数组 'remote_data' 并用 $() 中命令输出的值填充它 数组将自动按 space、制表符或新行拆分值。所以在这种情况下,我们将有 remote_data=( 6.9 mod_perl-2.0.4-11.el6_5.x86_64 )

然后将这些值赋值给变量:

rhelInfo=${remote_data[0]}
perlInfo=${remote_data[1]}

顺便说一句,echo(and cat)) 在这里有点矫枉过正,因此可以简化为:

...
remote_data=($(
    ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" \
        -o  StrictHostKeyChecking=no \
        -o PasswordAuthentication=no "
           awk 'END{print $7}' /etc/redhat-release
           rpm -qa | grep -i mod_perl
    "
))

rhelInfo=${remote_data[0]}
perlInfo=${remote_data[1]}
...

下面是我如何使用 ssh

远程数据 collection

一些评论

  • 因为此功能旨在收集数据,而不是连接主机(因为它们不会停留已连接),我已将它们重命名 ;-)

  • 避免无用的分叉!使用 printf -v varname ... 而不是 varname=$(printf ...)

  • STDERR 重定向 2>/dev/null 移动到更合适的位置。

  • 使用local将变量名保留到函数作用域中。

  • printf 必须有正确数量的参数,对应于 %s 的数量见 This format string has 3 variable....

  • 不要使用-i如果不需要则切换到grep

remote_collect() {
    local target_host= host_answer rhelInfo perlInfo
    {
        read -r host_answer
        read -r rhelInfo
        read -r perlInfo
    } < <(
        ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" \
            -o StrictHostKeyChecking=no -o PasswordAuthentication=no \
            /bin/sh <<-EOF
        hostname
        awk 'END{print }' </etc/redhat-release
        rpm -qa | grep mod_perl
EOF
    ) 2>/dev/null
    if [[ $? -eq 0 ]] ;then
        printf "| %-10s | %-14s | %-29s |\n" \
            "host_answer" "$rhelInfo" "$perlInfo"
    else
        printf "| %-10s | %-46s |\n" \
            "target_host" "Unable to get the ssh connection"
    fi
}

注意:EOF 标记之前可以有制表符 Tab,但 不能 spaces!我在发布之前删除了制表以确保它们不会在剪切和粘贴期间在 space 中转换。

标题可以是

printf -v marker '%61s' ''
marker=${marker// /-}
printf "+%s+\n| %-10s | %-14s | %-29s |\n+%s+\n" \
   "$marker" "Hostname" "RedHat Version" "Perl Version" "$marker" 
+-------------------------------------------------------------+
| Hostname   | RedHat Version | Perl Version                  |
+-------------------------------------------------------------+
| hostok     | 6.3            | mod_perl-2.0.4-10.el6_5.armf  |
| hostbad    | Unable to get the ssh connection               |

漂亮 table 使用 UFT-8 边框

printf -v sl %31s '';sl=${sl// /$'\U2500'}
printf '%b%-12s%b%-16s%b%-31s%b\n' \
    \U250c "${sl::12}" \U252c "${sl::16}" \U252c "$sl" \U2510 \
    \U2502 ' Hostname' \U2502 ' RedHat Version' \U2502 ' Perl Version' \
    \U2502   \U251c "${sl::12}" \U253c "${sl::16}" \U253c "$sl" \U2524

然后:

    if [[ $? -eq 0 ]] ;then
        printf "\U2502 %-10s \U2502 %-14s \U2502 %-29s \U2502\n" \
            "$host_answer" "$rhelInfo" "$perlInfo"
    else
        printf "\U2502 %-10s \U2502 %-46s \U2502\n" \
            "$target_host" "      Unable to get the ssh connection"
    fi

最后

# printf -v sl %31s '';sl=${sl// /$'\U2500'} # uncomment if $sl not at main scope
printf '\U2514%s\U2534%s\U2534%s\U2518\n' "${sl::12}" "${sl::16}" "$sl"

应该产生:

┌────────────┬────────────────┬───────────────────────────────┐
│ Hostname   │ RedHat Version │ Perl Version                  │
├────────────┼────────────────┼───────────────────────────────┤
│ hostok     │ 6.3            │ mod_perl-2.0.4-10.el6_5.armf  │
│ hostbad    │       Unable to get the ssh connection         │
└────────────┴────────────────┴───────────────────────────────┘

在我这边用@F 测试后,我将它保留在这里。 Hauri 回答..

#!/bin/bash
###########
printf -v sl %32s '';sl=${sl// /$'\U2500'}
printf '%b%-12s%b%-16s%b%-32s%b\n' \
    \U250c "${sl::12}" \U252c "${sl::16}" \U252c "$sl" \U2510 \
    \U2502 ' Hostname' \U2502 ' RedHat Version' \U2502 ' Perl Version' \
        \U2502   \U251c "${sl::12}" \U253c "${sl::16}" \U253c "$sl" \U2524

remote_collect() {
    target_host=
    {
        read -r rhelInfo
        read -r perlInfo
    } < <(
        ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" \
            -o StrictHostKeyChecking=no -o PasswordAuthentication=no \
            /bin/sh <<-EOF
        cat /etc/redhat-release | awk 'END{print $7}'
        rpm -qa | grep mod_perl
EOF
    ) 2>/dev/null

    if [[ $? -eq 0 ]] ;then
        printf "\U2502 %-10s \U2502 %-14s \U2502 %-28s \U2502\n" \
            "$target_host" "$rhelInfo" "$perlInfo"
    else
        printf "\U2502 %-10s \U2502 %-14s \U2502  %-29s \U2502\n" \
            "$target_host" "?" "Unable to connect"
    fi
 # printf -v sl %31s '';sl=${sl// /$'\U2500'} # uncomment if $sl not at main scope
 printf '\U2514%s\U2534%s\U2534%s\U2518\n' "${sl::12}" "${sl::16}" "$sl"

} 2>/dev/null
export -f remote_collect
< /home/zabbix/hostsList.txt  xargs -P30 -n1 -d'\n' bash -c 'remote_connect "$@"' --

输出 1:

┌────────────┬────────────────┬────────────────────────────────┐
│ Hostname   │ RedHat Version │ Perl Version                   │
├────────────┼────────────────┼────────────────────────────────┤
│ foxnl46    │ ?              │  Unable to connect             │
└┴┴┘
│ foxnl27    │ 6.7            │ mod_perl-2.0.4-11.el6_5.x86_64 │
└┴┴┘
│ foxnl32    │ 6.7            │ mod_perl-2.0.4-11.el6_5.x86_64 │

如果我删除最后一个 printf 一切都会好起来期待 table.

的最后一行

输出 2

┌────────────┬────────────────┬────────────────────────────────┐
│ Hostname   │ RedHat Version │ Perl Version                   │
├────────────┼────────────────┼────────────────────────────────┤
│ foxnl46    │ ?              │  Unable to connect             │
│ foxnl27    │ 6.7            │ mod_perl-2.0.4-11.el6_5.x86_64 │
│ foxnl32    │ 6.7            │ mod_perl-2.0.4-11.el6_5.x86_64 │