I have the below script:
bash Script:
#!/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=$1
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 $7}')
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 "$@"' --
The above script runs pretty well for me while running in parallel mode.
Script results:
|------------------------------------------------------------|
|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 |
Concern ?
I have two variables: rhelInfo
and perlInfo
to get store information. But it is using two ssh calls to the servers to get the values.
Could I have only one SSH call to execute multiple commands and set both variables?
CodePudding user response:
Could I have only one SSH call to execute multiple commands and set both variables?
Sure. You could do:
# 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 $7}' /etc/redhat-release)
perlinfo=$(rpm -qa | grep -i mod_perl)
# output elements separated by byte 0x01
printf "%s\001" "$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"
}
or similar variation of it. Basically ssh
gives you a bidirection stream of data - you can stream anything with a separator in between ("custom protocol") and then split the data on that separator. I.e. the problem is not limited to ssh - research data serialization/deserialization in bash. Above I have chosen the byte 0x01 to be the separator - you can use a separate line with unique uuid, use base64 -w0
to convert data to a single line, or similar or use another format.
remotework() {
awk ... | base64 -w0
echo ' '
rpm ... | base64 -w0
echo
}
...
IFS=' ' read -r rhelInfo perlInfo <<<"$tmp"
rhelInfo=$(<<<"$rhelInfo" base64 -d)
perlInfo=$(<<<"$perlInfo" base64 -d)
You can also serialize variables with declare -p
and then eval
them - this is in my opinion more dangerous, so it's better to use a separator.
CodePudding user response:
Suggesting to replace following lines:
rhelInfo=$(ssh -i /home/zabbix/.ssh/ssh_key "root@${target_host}" -o StrictHostKeyChecking=no -o PasswordAuthentication=no cat /etc/redhat-release| awk 'END{print $7}')
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
With following lines:
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
Explanation
First I call multi-line commands in the ssh
request. Placing all the commands in a single argument with "
(as you did).
The real trick is to read multi-line ssh
response into bash
array variable sshResponseArr
By default the array is parsed by
spaces.
I assume (as you did) that words positioning in the array is consistent across all hosts therefore:
$rhelInfo
is ${sshResponseArr[6]}
And $perlInfo
is ${sshResponseArr[8]}
Alternative conservative handling of sshResponseArr
as an array of 2 lines response from ssh
command:
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]}