0

I have some output that I need to parse into an array that looks like the following. The number of entries can change.

interface  : eth1
ip address : 1.1.1.1        [Active]
subnet mask: 255.255.255.0
router     : 1.1.1.2
name server: 1.1.1.3
dhcp server: 1.1.1.4
lease time : 86400
last update: Fri Jul 5 00:11:12 UTC 2013
expiry     : Sat Jul 06 00:11:08 UTC 2013
reason     : BOUND

interface  : eth2
ip address : 2.2.2.2        [Active]
subnet mask: 255.255.255.0
router     : 2.2.2.3
name server: 2.2.2.4
dhcp server: 2.2.2.5
lease time : 86400
last update: Fri Jul 5 03:03:41 UTC 2013
expiry     : Sat Jul 06 03:03:39 UTC 2013
reason     : REBOOT

Each section begins with interface and ends with reason and the blank line after reason.

I'm pretty new to bash scripting and have tried just about everything I can think of to get each section into a variable and I just can't seem to get it to work. If this was any other language... I could do this in a heartbeat!

Basically what I want is an array that will have each section with all of the details in between (these details can also change and not have as many lines).

I've tried a number of different methods with awk, sed, grep, etc... None of them seem to get me where I want to be.

What it should ultimately look like:

$output_array[$1]=
interface  : eth1
ip address : 1.1.1.1        [Active]
subnet mask: 255.255.255.0
router     : 1.1.1.2
name server: 1.1.1.3
dhcp server: 1.1.1.4
lease time : 86400
last update: Fri Jul 5 00:11:12 UTC 2013
expiry     : Sat Jul 06 00:11:08 UTC 2013
reason     : BOUND

$output_array[$2]=
interface  : eth1
ip address : 1.1.1.1        [Active]
subnet mask: 255.255.255.0
router     : 1.1.1.2
name server: 1.1.1.3
dhcp server: 1.1.1.4
lease time : 86400
last update: Fri Jul 5 00:11:12 UTC 2013
expiry     : Sat Jul 06 00:11:08 UTC 2013
reason     : BOUND

Can anyone point me in the right direction? Thanks!

One example of something I've tried, info was not split, or I did something wrong!

output_array=echo $output | awk -v x="^$" -v n=1 '$0 ~ x {n++; next}{print}'
for items in $output_array; do
echo "ENTRY: $items"
done
5
  • have tried just about everything I can think of -- could you post what you attempted. Commented Jul 5, 2013 at 3:57
  • If this was any other language... I could do this in a heartbeat! -- Very nice. Commented Jul 5, 2013 at 3:57
  • 1
    Use a while read loop to read it line by line, appending to some variable. When you find a blank line, add the variable to your array. Commented Jul 5, 2013 at 3:59
  • You want them in a bash array? Arrays in bash are a means to an end, and rather low on the list of useful features. If there's something in particular you want to do there's probably a better way. Commented Jul 5, 2013 at 4:03
  • devnull - Sure, I added an example of something that didn't work. That's one of many things I've tried over the last ~14 hours... I'm new to bash/shell scripting so try to laugh to much... very good with other languages. Kevin - Bash array, yes, this is a shell script, small part of a large script... and a means to an end. What's not useful to one person is perhaps very useful to another. Thanks for the comments. @that other guy - While read loop, I'll try to find an example of this, thanks. Commented Jul 5, 2013 at 4:24

4 Answers 4

1

One dirty way of doing it:

$ cnt=$(gawk -v RS='\n\n' 'END{print NR}' file)
$ for ((i=1;i<=cnt;i++)); do 
    a+=("$(gawk -v l="$i" -v RS='\n\n' 'NR==l' file)"); 
done

$ echo "${a[0]}"
interface  : eth1
ip address : 1.1.1.1        [Active]
subnet mask: 255.255.255.0
router     : 1.1.1.2
name server: 1.1.1.3
dhcp server: 1.1.1.4
lease time : 86400
last update: Fri Jul 5 00:11:12 UTC 2013
expiry     : Sat Jul 06 00:11:08 UTC 2013
reason     : BOUND

$ echo "${a[1]}"
interface  : eth2
ip address : 2.2.2.2        [Active]
subnet mask: 255.255.255.0
router     : 2.2.2.3
name server: 2.2.2.4
dhcp server: 2.2.2.5
lease time : 86400
last update: Fri Jul 5 03:03:41 UTC 2013
expiry     : Sat Jul 06 03:03:39 UTC 2013
reason     : REBOOT
Sign up to request clarification or add additional context in comments.

3 Comments

system doesn't have gawk :( not familiar enough to make it work with awk.
If you have Linux then you most likely have gawk. Awk points to Gawk
GNU bash, version 4.1.5(1)-release (mips-unknown-linux-gnu). While I'm sure I could install gawk, it's not really an option as the script will need to work on unmodified systems. However, I am sure this is a great way to do it if you have gawk! Thanks :)
1

Here's a hack for you:

IFS=$'\x01'
output_array=($(cat someoutput | sed -e "s/^$/$IFS/"))
IFS=$' \t\n'

It sets bash up to split words by an unprintable character, then inserts that unprintable character on all blank lines. Then it sets IFS back to its default so it doesn't interfere with the rest of your script.

3 Comments

I was trying to flatten the records with awk into an array in a similar concept but stopped when I was having trouble with | tr '\001' ' ' to get things into the bash array. +1 for making me realize it's unnecessary to put anything into an awk array here and introducing me to IFS. However, I can't get your code or my awk | tr to split up the records in the output array. I get two entries, each with all the output in one long line. Any insights?
This is because you do echo ${output_array[1]}, which puts it all on one line. Use quotes, echo "${output_array[1]}"
I tried that - I still only get one array element with all the data from this. Posting what I could get to work, but it's NOT pretty.
0

I've made it work using a suggestion from @that other guy and a bit more research

Can anyone improve on this?

output+=$'\n'
x=0
while read -r line
do
    if [ -z "$line" ]; then
        output_array[$x]=$data
        unset data
        let x++                
    else
        data+=$'\n'
        data+=$line
    fi
done <<< "$output"

for j in "${output_array[@]}"
do
    echo "$j"
done

Comments

0

@jivetek - this is NOT an improvement on what you've written, but it bothered me that I couldn't get a solution based on the solution @thatotherguy posted to work as I would expect. Here's a version that uses two unprintable chars, and some bash magic I don't understand. The caveat is that the "\002" character is left in the array elements. It would take a 2nd pass through the array to clean each element, but that might be easy enough to do somewhere else in your script ( presumably you need to walk that data anyway )

IFS=$'\001'
IN=`awk '/^$/ {print "\001"} { print $0 "\002" }' input`
IFS=$'\001\n' read -a oarr3 -d$IFS <<< $IN
IFS=$' \t\n'

where input is just your data in an file called "input".

Found the "read" command from this so question. The "magic" I don't understand is why the double assignment of the IFS works along with the "-d" flag when I don't think I should need anything like them.

The oarr3 contents are what I would expect( mostly ):

declare -a oarr3='([0]="interface  : eth1 ip address : 1.1.1.1        [Active] subnet mask: 255.255.255.0 router     : 1.1.1.2 name server: 1.1.1.3 dhcp server: 1.1.1.4 lease time : 86400 last update: Fri Jul 5 00:11:12 UTC 2013 expiry     : Sat Jul 06 00:11:08 UTC 2013 reason     : BOUND " [1]=" interface  : eth2 ip address : 2.2.2.2        [Active] subnet mask: 255.255.255.0 router     : 2.2.2.3 name server: 2.2.2.4 dhcp server: 2.2.2.5 lease time : 86400 last update: Fri Jul 5 03:03:41 UTC 2013 expiry     : Sat Jul 06 03:03:39 UTC 2013 reason     : REBOOT")'

However, like I said, the "\002" char is still in each array element, and there could also be a space after the "\002" char:

echo "${oarr3[0]}" | tr '\002' '\n'
interface  : eth1
 ip address : 1.1.1.1        [Active]
 subnet mask: 255.255.255.0
 router     : 1.1.1.2
 name server: 1.1.1.3
 dhcp server: 1.1.1.4
 lease time : 86400
 last update: Fri Jul 5 00:11:12 UTC 2013
 expiry     : Sat Jul 06 00:11:08 UTC 2013
 reason     : BOUND
[0]

here's a view of the raw data:

cat -etv <<< ${oarr3[0]} interface  : eth1^B ip address : 1.1.1.1        [Active]^B subnet mask: 255.255.255.0^B router     : 1.1.1.2^B name server: 1.1.1.3^B dhcp server: 1.1.1.4^B lease time : 86400^B last update: Fri Jul 5 00:11:12 UTC 2013^B expiry     : Sat Jul 06 00:11:08 UTC 2013^B reason     : BOUND^B $

Also looks like there's a leading "\002" in the 2nd element. That's likely because I had to leave in the returns from the awk output and include them in the 2nd IFS declare. That could all be fixed by re-awking when the data needs to processed.

Shell version:

sh -version
GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.