Reading delimited output into multiple Bash variables

Reading delimited output into multiple Bash variables



This should be simple and I've done it in a script somewhere before, I can't find my example (or an equivalent), and today this problem is driving me toward insanity. (Even tho I've not included the rest of script this is for use inside a script, not interactive.)


cat testfile | grep -e eth0



Returns:


eth0 123.45.67.8/23 u/u Internet - Cable WAN



The end result is I need variables set for each element. i.e. as if I had done this manually instead:


INTF = "etho"
IPADDR = "123.45.67.8/23"
STS = "u/u"
DESC = "Internet - Cable WAN"



I thought I could do something like:


cat testfile | grep -e eth0 | awk 'print $2' | xargs read IPADDR



or


cat testfile | grep -e eth0 | cut -d " " -n2 | read IPADDR



but nothing I've tried has brought joy.... What is my roadblock (headblock)?



EDIT to add— the script is more complicated than just grabbing one IP, as my example is leading people to conclude. It’s a cron based script that runs once per minute, it runs a loop thru 8 interfaces and sends a message in certain alarm conditions. The rest of the script works when I run it with hard coded variables, I just cut asked about the part that is stumping me.





Why not just use IPADDR=$(cat outputfile | grep -e eth0 | awk 'print $2')?
– Néstor Lucas Martínez
Aug 25 at 1:36


IPADDR=$(cat outputfile | grep -e eth0 | awk 'print $2')





BTW, in your description of the problem you have used outputfile for the name of the file in the first example, and testfile in the second. Be careful with that.
– Néstor Lucas Martínez
Aug 25 at 1:38


outputfile


testfile





@NéstorLucasMartínez That’s a possibility and I hadn’t thought of assigning that way, however my example is oversimplified as this is a cron based script that runs every minute and cycles thru 8 interfaces. (It’s not just a script to grab a single IP and go.) Granted I don’t actually need to set INTF because it’s already known at that point, but seems inefficient to do as suggested 3 times in every loop.
– Tyson
Aug 25 at 1:52





Then you may try read INTF IPADDR STS DESC <<< `cat testfile | grep -e eth0`
– Néstor Lucas Martínez
Aug 25 at 1:57



read INTF IPADDR STS DESC <<< `cat testfile | grep -e eth0`




4 Answers
4



Since you want to set 4 variables, instead of doing cut 4 times, you can use read like this:


cut


read


#!/bin/bash
#
read INTF IPADDR STS DESC <<< $(cat testfile | grep -e eth0)

echo $INTF
echo $IPADDR
echo $STS
echo $DESC



This will "cut" on any white space, using the default $IFS.


$IFS



If you wanted to cut values from: "aaa,bbb,ccc,ddd",

you can change the IFS value before the read.


read



Ex:


IFS="," read INTF IPADDR STS DESC <<< $(cat testfile | grep -e eth0)





this is exactly what I was trying to do, read all 4 with a single read. This works. Bash isn't my normal environment, sub-shell's always get me in trouble.
– Tyson
Aug 25 at 18:54



If you want to get all the variables assigned at once using read, you can do it as follows:


read


read INTF IPADDR STS DESC <<< `cat testfile | grep -e eth0`



Tons of ways to do this, You could even scan over your test file and put the results into arrays one to one matching:


#!/bin/bash
file="testfile.txt"

declare -a intf
declare -a ipaddr
declare -a sts
declare -a desc

# - file reader -
while IFS= read -r line; do
if [[ $line,, == *"intf"* ]];then
intf+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)")
elif [[ $line,, == *"ipaddr"* ]];then
ipaddr+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)")
elif [[ $line,, == *"sts"* ]];then
sts+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)")
elif [[ $line,, == *"desc"* ]];then
desc+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)")
fi
done < "$file"

if [[ $#intf[@] -eq $#ipaddr[@] && $#intf[@] -eq $#ipaddr[@] && $#intf[@] -eq $#sts[@] ]] ;then
echo "file read successful! continuing .."
else
echo "icky, this didn't work"
fi

for ((i=0; i< $#intf[@]; i++)) ;do
echo -e "INTF=$intf[$i] nIPADDR=$ipaddr[$i] n"

done



output (something like):


$ ./script.sh
file read successful! continuing ..
INTF=etho
IPADDR=123.45.67.8/23

INTF=etho
IPADDR=13.45.67.8/23

INTF=etho
IPADDR=23.45.67.8/23





Added a paragraph to the question, better explaining the need, not trying to just grab the external IP of a single internet connection.
– Tyson
Aug 25 at 2:03





thanks I misread your question , above is one way to make arrays and check that you have a one to one relationship after scanning a file.
– Mike Q
Aug 25 at 2:56





Can desc+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)") etc., be more efficiently written with read, without needing to invoke a subshell?
– codeforester
Aug 27 at 1:57



desc+=("$(echo $line | cut -d'"' -f2- | cut -d'"' -f1)")


read





Yes there is probably no reason to use an echo
– Mike Q
Aug 31 at 1:35



In your commands


cat testfile | grep -e eth0 | cut -d " " -n2 | read IPADDR
cat testfile | grep -e eth0 | awk 'print $2' | xargs read IPADDR



the read command runs in a subshell and it won't be visible to the main shell.



You need process substitution to get the values into the parent shell in one go:


read -r intf ipaddr sts desc < <(grep eth0 testfile)



The first three variables would get the value of the first three fields in grep's output (which is string split based on IFS) and the fourth variable desc will get the remaining tokens in the output.


desc



As an aside, cat outfile | grep -e eth0 is a case of UUOC. Just grep eth0 outfile would do your job.


cat outfile | grep -e eth0


grep eth0 outfile



Related:






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)