store command output in variable - bash

I am working on a script that executes ssh to few systems (listed in lab.txt), run two commands, store the output of commands in two different variables and print them.
Here is the script used :
#!/bin/bash
while read host; do
ssh -n root#$host "$(STATUS=$(awk 'NR==1{print $1}' /etc/*release) \
OS=$(/opt/agent/bin/agent.sh status | awk 'NR==1{print $3 $4}'))"
echo $STATUS
echo $OS
done < lab.txt
The lab.txt file contains few Ips where I need to login, execute and print the command output.
~#] cat lab.txt
192.168.1.1
192.168.1.2
While executing the script, the ssh login prompt of 192.168.1.1 is shown and after entering the password, the output is shown blank. Same as for next ip 192.168.1.2
When I execute these command manually within 192.168.1.1, the following is returned.
~]# awk 'NR==1{print $1}' /etc/*release
CentOS
~]# /opt/agent/bin/agent.sh status | awk 'NR==1{print $3 $4}'
isrunning
What could be wrong with the script? Is there a better way of doing this?

As the comment says, you are setting the variables inside the bash session on the server side and trying to read them from the client side.
If you want to assign the variables in the client script you need to put the assignment in front of the ssh command, and separate the two assignments. Something like the following.
STATUS=`ssh -n root#$host 'awk \'NR==1{print $1}\' /etc/*release)`
OS=`ssh -n root#$host '/opt/agent/bin/agent.sh status | awk \'NR==1{print $3 $4}\''`

You need to do two ssh commands. It also simplifies things if you run awk on the client rather than the server, because quoting in the ssh command gets complicated.
while read host; do
STATUS=$(ssh -n root#$host 'cat /etc/*release' | awk 'NR==1{print $1}')
OS=$(ssh -n root#$host /opt/agent/bin/agent.sh status | awk 'NR==1{print $3 $4}')
echo $STATUS
echo $OS
done < lab.txt

with one ssh statement:
read STATUS OS < <(ssh -n root#$host "echo \
\$(awk 'NR==1{print \$1}' /etc/*release) \
\$(/opt/agent/bin/agent.sh status | awk 'NR==1{print \$3 \$4}')")
echo $STATUS
echo $OS
Explanation:
The <(command) syntax is called process substitution. You can use it anywhere where a file is expected.
Example:
sdiff <(echo -e "1\n2\n3") <(echo -e "1\n3")
The command sdiff expects two files as arguments. With the process substitution syntax you can use commands as arguments. ( e.g. fake files )

Related

Bash: Pass variable to other server in shell script

I have controller node (CN) and server nodes (SN). From CN, I need to config the hostname of each and every SN. inv file contains list of SNs and corresponding hostname.
In below script, I am not sure how to pass hostname variable. Any best way to achieve same?
#!/bin/bash
IFS='\n'
for data in `awk 'NR>1 {print}' inv`
do
ip=`awk '{print $1}' <<< $data`
hostname=`awk '{print $2}' <<< $data`
ssh root#$ip "hostnamectl set-hostname $hostname"
done
inv file,
IPs Hostname
172.31.98.11 Server1
172.31.106.177 Server2
172.31.97.105 Server3
There is no need to use awk here since you are just printing all rows after 1st record. A tail does that job.
You may use this bash script:
while read -r ip hn; do
ssh root#"$ip" "hostnamectl set-hostname $hn"
done < <(tail -n +2 inv)

Multiple whois lookup

I have the below script for whois lookup
for line in $(cat ips.txt)
do
echo $line
whois $line | grep OrgName | awk '{print $2,$NF}'
done
I am having the output
192.168.1.1
Internet Authority
How can I achieve the output in the below format ?
192.168.1.2 : Internet Authority
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04 LTS"
On the 'echo $line' line, the shell was asked to print the value of $line. The shell says ok - done.
Then the shell moves on to the next line, that basically says 'get string then pipe it to some string manipulation and print result'.
I believe 'print something on the screen' was asked from the shell twice, 1 by echo 2 by awk, which are from 2 separate lines , so the shell behaved as expected.
To prevent this you can contain the second line in $(), so that echo will print "$line + $(whatever comes out here)"
for line in $(cat ips.txt)
do
echo $line : $(whois $line | grep OrgName | awk '{print $2,$NF}')
done

Parsing command output in a bash script

I want to parse the output of docker node ls -f name=manager.
On bash prompt this is how it looks
ID HOSTNAME STATUS AVAILABILITY
kdrdpvwlbwai6626u640sotnh * manager Ready Active
I want to print an error message if the STATUS is not Read i.e. if in grep the match is not found for the word "Ready".
I tried but the command docker node ls -f name=manager due to spaces and arguments is treated as more than one command in the script for some reason.
What is the right way to do this?
The conventional method of doing this would be
#docker command
if [ $? -ne 0 ]
then
echo "Docker error"
fi
Edit
Supposing you get two lines of output, below would do
count=$(docker node ls -f name=manager | tail -n 1 | grep -c 'Ready[[:blank:]]*[^[:blank:]]*$')
if [ $count -ne 1 ]
then
echo "Docker Error"
fi
By default, Bash recognizes array values as separated by blank characters.
It hold a variable called IFS with the separator, and in case you want to change it, you have to change this variable.
The regular process is: storing the old value of this var, change it, process the data and restore it:
OFS=$IFS
# for new line there is special var: $'\n'
IFS=$'\n'
#your code here
IFS=$OFS
SO you can change the IFS, read the whole lines as one value and than process it with substitution: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
Another option is using awk (this command will print host and status):
docker node ls -f name=manager | awk '{print $2, $3}'
OR refine you search and get the hosts that status is not ready:
docker node ls -f name=manager | awk -F' '{if ($3 != "Ready") print $2}'
In case you have only one host, you can use grep to print if it found or not:
docker node ls -f name=manager | grep -iq "not ready" && echo "Not found" || echo "Found"
or omit the ||echo "Found" if you want to print an error message only

awk for different delimiters piped from xargs command

I run an xargs command invoking bash shell with multiple commands. I am unable to figure out how to print two columns with different delimiters.
The command is ran is below
cd /etc/yp
cat "$userlist" | xargs -I {} bash -c "echo -e 'For user {} \n'
grep -w {} auto_*home|sed 's/:/ /' | awk '{print \$1'\t'\$NF}'
grep -w {} passwd group netgroup |cut -f1 -d ':'|sort|uniq;echo -e '\n'"
the output I get is
For user xyz
auto_homeabc.jkl.com:/rtw2kop/xyz
group
netgroup
passwd
I need a tab after the auto_home(since it is a filename) like in
auto_home abc.jkl.com:/rtw2kop/xyz
The entry from auto_home file is below
xyz -rw,intr,hard,rsize=32768,wsize=32768 abc.jkl.com:/rtw2kop/xyz
How do I awk for the first field(auto_home) and the last field abc.jkl.com:/rtw2kop/xyz? As I have put a pipe from grep command to awk.'\t' isnt working in the above awk command.
If I understand what you are attempting correctly, then I suggest this approach:
while read user; do
echo "For user $user"
awk -v user="$user" '$1 == user { print FILENAME "\t" $NF }' auto_home
awk -F: -v user="$user" '$1 == user { print FILENAME; exit }' passwd group netgroup | sort -u
done < "$userlist"
The basic trick is the read loop, which will read a line into the variable $user from the file named in $userlist; after that, it's all straightforward awk.
I took the liberty of changing the selection criteria slightly; it looked as though you wanted to select for usernames, not strings anywhere in the line. This way, only lines will be selected in which the first token is equal to the currently inspected user, and lines in which other tokens are equal to the username but not the first are discarded. I believe this to be what you want; if it is not, please comment and we can work it out.
In the 1st awk command, double-escape the \t to \\t. (You may also need to double-escape the \n.)

How to convert from command line to bash script?

I have a chain of commands that can execute all at once, however I wish to put it inside of a bash script. The problem is that I have no clue how to. My command is like so:
/usr/bin/sort -n db | /usr/bin/awk -F: '{print $1; print $2}' | db5.1_load -T -t hash newdb
How can I convert the above into a bash script?
This should normally be as simple as putting the shell command into a text file, and putting the Unix shebang on the first line of the file, which defines which program to use to run the script (in this case, /bin/bash). So this would look like:
#!/bin/bash
/usr/bin/sort -n db | /usr/bin/awk -F: '{print $1; print $2}' | db5.1_load -T -t hash newdb

Resources