printf in bash sometimes prints new line, can't figure out why - bash

if i have a file with below contents:
127.0.0.1
127.0.0.2
127.0.0.3
8.8.8.8
127.0.0.4
and run this command to quickly test SSH connection success or failure:
while read host
do
ssh -n -q -o BatchMode=yes -o ConnectTimeout=5 $host "echo 2>&1" && \
printf "%-15s%-45s%s" `id -un` $host "SSH_OK" || \
printf "%-15s%-45s%s" `id -un` $host "SSH_BAD"
done <host_list
I get:
myname 127.0.0.1 SSH_OK
myname 127.0.0.2 SSH_OK
myname 127.0.0.3 SSH_OKmyname 8.8.8.8 SSH_BAD
myname 127.0.0.4 SSH_OK[myname#server ~]$
As you can see, it looks nasty because it doesn't print a newine for the SSH_BAD output or the last line so bash prompt is tagged to end of last check.
I can fix this by adding a \n to the prinf but then i have double spaces on everything except for the BAD line.
Can someone tell me please where is printf getting the newline from when i don't specify one, and why it prints one for the SSH_OK lines but not the SSH_BAD lines.
Is there a good/easy way to fix this?
thank you
fLo

The newlines are coming from echo 2>&1 that you're running on the remote system. When the ssh is successful, this command executes, and you get a newline printed before the printf. When the ssh fails, the command doesn't run, so you get no newline before the printf.
I suggest you use a command that doesn't display anything, and then put \n in both printf lines:
while read host
do
ssh -n -q -o BatchMode=yes -o ConnectTimeout=5 $host "true" && \
printf "%-15s%-45s%s\n" `id -un` $host "SSH_OK" || \
printf "%-15s%-45s%s\n" `id -un` $host "SSH_BAD"
done <host_list

Related

variables in remote nested server via ssh

Trying to set and access some variables on a remote server
the script will execute on a local server, it will login to remote-server1 and then login again to another second remote server remote-server2
I can successfully set and access variables in both local and remote-server1 without any issues, but having problem doing the same on remote-server2
local1=$(echo "local-srv"); echo "${local1}"
output=$(sshpass -p "${PSSWD}" ssh -t -q -oStrictHostKeyChecking=no admin#$mgmtIP "bash -s" <<EOF
remote1=\$(echo "remote-srv1"); echo "\${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
remote2=\$(echo "remote-srv2"); echo "\${remote2}"
EOF2
EOF
)
Here is my output
local-srv
remote-srv1
as you can see remote-srv2 is missing
----- UPDATE ---
please note that $(echo "text") is just for simplicity but a complex command will be executed here and the output set to a variable
You have two nested ssh commands with nested here-documents, and to delay interpretation of the $ expressions in the inner one, you need more escapes. To see the problem, you can replace the ssh command with cat to see what would be sent to the remote computer. Here's an example, using your original code (and some modified variable definitions); note that the $ and > are prompts from my shell.
$ targetCompute=remote-server2
$ local1="local-srv"; echo "${local1}"
local-srv
$ cat <<EOF
> remote1=\$(echo "remote-srv1"); echo "\${remote1}"
> ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
> remote2=\$(echo "remote-srv2"); echo "\${remote2}"
> EOF2
> EOF
remote1=$(echo "remote-srv1"); echo "${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "remote-server2" "bash -s" <<EOF2
remote2=$(echo "remote-srv2"); echo "${remote2}"
EOF2
Notice that the lines relating to remote1 and remote2 have both had their escapes removed, so they're both going to have their $ expressions expanded on remote-srv1. That's what you want for the remote1 line, but to delay interpretation of the remote2 line you have to add another escape... and that escape itself needs to be escaped, so there'll actually be three escapes before each $:
$ cat <<EOF
> remote1=\$(echo "remote-srv1"); echo "\${remote1}"
> ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
> remote2=\\\$(echo "remote-srv2"); echo "\\\${remote2}"
> EOF2
> EOF
remote1=$(echo "remote-srv1"); echo "${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "remote-server2" "bash -s" <<EOF2
remote2=\$(echo "remote-srv2"); echo "\${remote2}"
EOF2
So \\\$(echo "remote-srv2") and "\\\${remote2}" in the local here-document become \$(echo "remote-srv2") and "\${remote2}" in the here-document on remote-srv1, and then the command actually gets executed and the variable expanded on remote-srv2.
I needed to escape by ///
Thanks to the answer given by #Goron Davisson
local1=$(echo "local-srv"); echo "${local1}"
output=$(sshpass -p "${PSSWD}" ssh -t -q -oStrictHostKeyChecking=no admin#$mgmtIP "bash -s" <<EOF
remote1=\$(echo "remote-srv1"); echo "\${remote1}"
ssh -t -q -oStrictHostKeyChecking=no "${targetCompute}" "bash -s" <<EOF2
remote2=\\\$(echo "remote-srv2"); echo "\\\${remote2}"
EOF2
EOF
)
Output
local-srv
remote-srv1
remote-srv2

Proper way to show which SSH commands you are running

I am trying to pass an array of commands to a NID device via SSH, then storing the output into a variable. I can't figure out a efficient way to display which command is running in the output.
I can get it working by looping the the array and doing 7 separate SSH sessions. Which is very slow.
n_info=$(sshpass -p "-PW-" ssh -q -o StrictHostKeyChecking=false admin#$nid_ip << EOF
${c_array[0]}
${c_array[1]}
${c_array[2]}
${c_array[3]}
${c_array[4]}
${c_array[5]}
${c_array[6]}
exit
EOF
)
echo "$i"
echo "$n_info"| sed "s/ACCEDIAN:>//g"
Expected:
[show log]
log text
log text
log text
[show config]
config text
config text
config text
Actual:
log text
log text
log text
config text
config text
config text
Commands are not static**
Simplest/cleanest solution, if it's possible, is to run the echo commands on the nid.
Assuming headers contain no shell special characters:
sshpass -p "-PW-" ssh -q -o StrictHostKeyChecking=false admin#$nid_ip << EOF
echo '[${h_array[0]}]'; ${c_array[0]}
echo '[${h_array[1]}]'; ${c_array[1]}
echo '[${h_array[2]}]'; ${c_array[2]}
echo '[${h_array[3]}]'; ${c_array[3]}
echo '[${h_array[4]}]'; ${c_array[4]}
echo '[${h_array[5]}]'; ${c_array[5]}
echo '[${h_array[6]}]'; ${c_array[6]}
exit
EOF | sed "s/ACCEDIAN:>//g"
If you are using openssh as the ssh client, it has options to use a single control connection. Something like:
REMOTE="admin#$nid"
CP_DIR="/tmp/sshctl/$$"
CP="$CP_DIR/"%L-%r#%h:%p"
mkdir -p "$CP_DIR"
sshpass -p "-PW-" \
ssh -q -nNf -o ControlMaster=yes -o ControlPath="${CP}" \
-q -o StrictHostKeyChecking=false "$REMOTE"
# !!! check you have a connection !!!
# it may work to wrap "sshpass ..." with "if ! sshpass ...; then do_error; fi"
(
for i in {0..6}; do
echo "[$h_array[$i]}]"
ssh -o ControlPath="${CP}" \
-q -o StrictHostKeyChecking=false "$REMOTE" "${c_array[$i]}"
done
ssh -O exit -o ControlPath="$CP" "$REMOTE"
) | sed "s/ACCEDIAN:>//g"
rm -r "$CP_DIR"

How to check connection to a list of servers in bash?

Im trying to check connections for a list of servers. I want to loop through the list, check if a connection works and if yes, do some stuff, if not, echo out a problem message.
My problem is:
the script stops at the first node without echoing the $?.
So, whats wrong with my for-loop?
These vars are included from a config file:
$nodes is a list of server IPs like 1.1.1.1,2.2.2.2,10.10.10.10
$user is one string
for node in $(echo $nodes | sed "s/,/ /g")
do
echo "Checking Node: $node"
ssh -q -o ConnectTimeout=3 $user#$node echo ok
echo $?
if [[ $? != 0 ]]
then
echo "Problem in logging into $node"
else
# do some stuff here
fi
done
EDIT #1:
for node in $(echo $nodes | sed "s/,/ /g")
do
echo "Checking Node: $node"
ssh -q -t -o ConnectTimeout=3 $user#$node "echo ok"
retcode=$?
echo $retcode
if [[ "$retcode" -ne 0 ]]
then
echo "Problem in logging into $node"
else
echo "OK"
fi
done
It is because ssh first asks you to validate The authority of the host and If you accept the authority it will ask for password. That is why your command does not return to shell and waits for input.
If your intention is just validating ssh connection, then you may consider to use
telnet <your_host> <port> < /dev/null
But if your intend is to run some commands you need a trust relationship between hosts. In that case you can use:
Execute this commands:
ssh-keygen -t rsa
then
ssh-copy-id -i root#ip_address
Now you can connect with
ssh <user>#<host>
Furher information
You can add -tto make virtual terminal and add quotes on command:
ssh -q -t -o ConnectTimeout=3 ${user}#${node} "echo ok"
Also use -ne instead of != which is for compare strings
if [[ "$?" -ne 0 ]]
Also echo $? mess the return code. You should use something like:
ssh -q -t -o ConnectTimeout=3 ${user}#${node} "echo ok"
retcode=$?
echo $retcode
if [[ "$retcode" -ne 0 ]]
You can rewrite ssh command like this to avoid problems with ssh host keys
ssh -q -t -o StrictHostKeyChecking=no -o ConnectTimeout=3 ${user}#${node} "echo ok"

Variable Not Picking up when in Quotes

I'm trying to rsync a DIR from one Server to 100s of Servers using script (Bottom)
But, When i put single or double quotes around ${host} variable, Host names are not picked properly or not resolved.
Error is like below
server1.example.com
Host key verification failed.
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: unexplained error (code 255) at io.c(600) [sender=3.0.6]
and when I run only with rync command like below, It works. But, Output doesn't contain hostname which is important for me to correlate the output with associated hostname.
hostname -f && rsync -arpn --stats /usr/xyz ${host}:/usr/java
Can you please review and suggest me how to make the script work even with quotes around Host variable. ?
So, that , Output will contain hostname and output of rsync together.
==============================================
#!/bin/bash
tmpdir=${TMPDIR:-/home/user}/output.$$
mkdir -p $tmpdir
count=0
while IFS= read -r host; do
ssh -n -o BatchMode=yes ${host} '\
hostname -f && \
rsync -arpn --stats /usr/xyz '${host}':/usr/java && \
ls -ltr /usr/xyz'
> ${tmpdir}/${host} 2>&1 &
count=`expr $count + 1`
done < /home/user/servers/non_java7_nodes.list
while [ $count -gt 0 ]; do
wait $pids
count=`expr $count - 1`
done
echo "Output for hosts are in $tmpdir"
exit 0
UPDATE:
Based on observation with (set -x), Host name is being resolved on remote (self) it self, it supposed to be resolved on initiating host. I think Once we know how to make host name resolved with in initiating host even when quotes are in place.
As far as I can tell, what you're looking for is something like:
#!/bin/bash
tmpdir=${TMPDIR:-/home/user}/output.$$
mkdir -p "$tmpdir"
host_for_pid=( )
while IFS= read -r host <&3; do
{
ssh -n -o BatchMode=yes "$host" 'hostname -f' && \
rsync -arpn --stats /usr/xyz "$host:/usr/java" && \
ssh -n -o BatchMode=yes "$host" 'ls -ltr /usr/java'
} </dev/null >"${tmpdir}/${host}" 2>&1 & pids[$!]=$host
done 3< /home/user/servers/non_java7_nodes.list
for pid in "${!host_for_pid[#]}"; do
if wait "$pid"; then
:
else
echo "ERROR: Process for host ${host_for_pid[$pid]} had exit status $?" >&2
fi
done
echo "Output for hosts are in $tmpdir"
Note that the rsync is no longer inside the ssh command, so it's run locally, not remotely.

execute commands and store command output to variable

This script reads the system IP from hosts.txt, login to system, checks the OS type, executes a set of commands and print the output.
The ssh portion works fine, however the error (No such File or Directory) is shown after displaying output for 'ls''.
It seems the command /opt/hyperic/agent-current/bin/hq-agent.sh is not getting executed on remote host. The intend is to execute commands 'cd /opt;ls and also capture the command output to STATUS on each remote systems mentioned in host.txt.
When I run manaully the command on the remote system, the below output is returned. Any help on what could be wrong here?
~]# /opt/hyperic/agent-current/bin/hq-agent.sh status | awk 'NR==1{print $3 $4}'
isrunning
script is as below
#!/bin/bash
while read host; do
if ssh -o StrictHostKeyChecking=no -n root#$host '[ "$(awk "/CentOS/{print}" /etc/*release)" ] '
then
echo "(centos)"
ssh -o StrictHostKeyChecking=no -n root#$host 'cd /opt;ls'
STATUS=`/opt/hyperic/agent-current/bin/hq-agent.sh status | awk 'NR==1{print $3 $4}'`
if [ "$STATUS" == "isrunning" ]
then
echo "$HOST == PASS"
else
echo "$HOST == FAIL"
fi
else
echo "(generic)"
fi
done < hosts.txt
Output of the script --
root#10.10.1.1's password:
firstboot
puppet
./hq-enhanced.sh: line 14: /opt/hyperic/agent-current/bin/hq-agent.sh: No such file or directory
== FAIL
Ahh... I see what is happening:
Key to the question is:
ssh -o StrictHostKeyChecking=no -n root#$host 'cd /opt;ls'
STATUS=`/opt/hyperic/agent-current/bin/hq-agent.sh status | awk 'NR==1{print $3 $4}'`
the ssh command runs immediately and returns control to the script - at this point your not logged in via ssh anymore and the line starting STATUS executes wherever your running your script from.
To capture the output of the ssh command you would need something like:
STATUS=`ssh root#foobar -c 'cd /foo/bar && /opt/hyperic/agent-current/bin/hq-agent.sh ...'`
HTH

Resources