Reputation: 199
I have a response
variable that has the following strings in it:
response
variable (this output I pulled from a telnet session, which I'm not showing below in the code for the sake of simplifying):
mydummyhost# show ip bgp 43.245.43.105
BGP routing table entry for 43.245.43.0/24
Paths: (2 available, best #1, table Default-IP-Routing-Table)
Not advertised to any peer
38561 2914 55432, (aggregated by 55532 202.68.67.134)
202.158.215.44 from 201.158.202.44 (202.158.215.62)
Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001
Last update: Tue Sep 22 12:25:17 2020
38561 2914 55433, (aggregated by 55433 202.68.67.135)
202.158.215.52 from 202.158.215.52 (202.158.215.62)
Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001
Last update: Mon Sep 21 06:44:58 2020
I have a piece of code that I'm using to try to iterate through the string lines above and basically get the following result :
Required result:
43.245.43.105 is domestic peering (On-Net) originated by AS 55432 via path 38561 2914 55432
43.245.43.105 is domestic peering (On-Net) originated by AS 55433 via path 38561 2914 55433
Code:
#!/usr/bin/env ruby
require 'net/telnet'
ipaddress = "43.245.43.105"
cat = []
response = ""
origin = []
paths = []
net = []
community = []
onoffnet= {
ond:"domestic (On-Net)",
oni:"international research (On-Net)",
opd:"domestic peering (On-Net)",
ofd:"domestic transit (Off-Net)",
opi:"international peering (Off-Net)",
ofi:"international transit (Off-Net)"
}
response.each_line do |line|
if line =~ /BGP routing table entry for (\d+\.\d+\.\d+\.\d+.*)/
net[i] = $1
elsif line =~ /Community: (.*)$/
community[i] = $1
elsif line =~ /^\s+([0-9\ ]+),.*/
paths, aggregatedBy = line.split(", ")
paths[i] = paths.strip
origin[i] == aggregatedBy.split(" ")[2]
elsif line =~ /Last update:/
i += 1
end
end
if i == 0
print "ERROR, no data found for the IP."
else
i = 0
net.each do | ip |
if community[i] =~ /7575\:1000/
cat[i] = onoffnet.fetch(:ond)
elsif community[i] =~ /7575\:1001/
cat[i] = onoffnet.fetch(:oni)
elsif community[i] =~ /7575\:1002/
if community[i] =~ /7575\:6001/
cat[i] = onoffnet.fetch(:opd)
else
cat[i] = onoffnet.fetch(:opi)
end
elsif community[i] =~ /7575\:1003/
if community[i] =~ /7575\:6001/
cat[i] = onoffnet.fetch(:ofd)
else
cat[i] = onoffnet.fetch(:ofi)
end
end
i += 1
if origin[i].to_s.length > 0 && paths[i].to_s.length > 0
puts "#{ipaddress} is cat[i] network ip[i] originated by AS #{origin} via path #{paths} ."
else
puts #{ipaddress} + "is" + cat[i] + "network" + ip[i] + "\n"
puts "Test"
end
end
end
When I ran this, only "Test" is shown as output :
[root@mydummyhost]# ./telnet.rb
Test
Basically I'm trying to use line =~ /Last update:/
to determine that I have another separate paths and origin to display by incrementing i
, if that makes sense.
So I know it had hit the if
condition, however I'm not sure why its not showing the first print line puts #{ipaddress} + "is" + cat[i] + "network" + ip[i] + "\n"
, this appears to be empty.
After changing puts #{ipaddress} + "is" + cat[i] + "network" + ip[i] + "\n"
to puts "#{ipaddress} is #{cat[i]} network #{ip[i]}\n"
(as suggested by @trueunlessfalse thanks!), I can now see an output :
[root@dummyhost]# ./telnet.rb
43.245.43.105 is network 3
Unfortunately this isnt even close to the required result I'm looking for.
I know somehow the problem is with the way I was iterating that response
var and populating the arrays - I have a perl code that works and am trying to convert that to ruby - , I'm just not sure how to fix this or what is a better way of going through that output.
Any suggestions how I should iterate response
so the arrays can be populated based on the i
value that gets incremented when it comes to the end of the "block" marked by Last update:
?
Thanks J
Upvotes: 0
Views: 95
Reputation: 1233
Update: This answer is not adressing all issues with the authors code, but only the question why one of the puts
is not giving any output.
I would have expected this line:
puts #{ipaddress} + "is" + cat[i] + "network" + ip[i] + "\n"
to throw an error, because I never before tried to interpolate a string without first opening a string.
I tried this in the console, and indeed it just prints an empty line:
irb(main):002:0> a = "foo"
=> "foo"
irb(main):003:0> puts #{a}
=> nil
However this works as expected:
irb(main):004:0> puts "#{a}"
foo
=> nil
Try rewriting this line as:
puts "#{ipaddress} is #{cat[i]} network #{ip[i]}\n"
Upvotes: 2
Reputation: 110685
The expected result appears to depend on the following types of values contained in the given string:
"43.245.43.105"
)"38561 2914 55432"
)"38561"
)"7575:1002 7575:2462 7575:3002 7575:6001 7575:8001"
)I suggest you first concentrate on extracting these values, after which it is fairly straightforward to construct the desired strings. My answer is limited to this initial task.
To produce some numbers I will first construct your string response
.
response =<<~BITTER_END
mydummyhost# show ip bgp 43.245.43.105
BGP routing table entry for 43.245.43.0/24
Paths: (2 available, best #1, table Default-IP-Routing-Table)
Not advertised to any peer
38561 2914 55432, (aggregated by 55532 202.68.67.134)
202.158.215.44 from 201.158.202.44 (202.158.215.62)
Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001
Last update: Tue Sep 22 12:25:17 2020
38561 2914 55433, (aggregated by 55433 202.68.67.135)
202.158.215.52 from 202.158.215.52 (202.158.215.62)
Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001
Last update: Mon Sep 21 06:44:58 2020
BITTER_END
I don't know much about Telnet, but it appears response
contains a block of data that begins with a line that starts with 'mydummyhost'. I have written this to permit multiple such blocks (each beginning with a line that begins, 'mydummyhost'), so as a first step I will apply String#scan with a regular expression as follows.
arr = response.scan(/^mydummyhost\D+.+?(?=\z|^mydummyhost)/m)
#=> ["mydummyhost# show ip bgp 43.245.43.105\nBGP routing table entry for 43.245.43.0/24\nPaths: (2 available, best #1, table Default-IP-Routing-Table)\n Not advertised to any peer\n 38561 2914 55432, (aggregated by 55532 202.68.67.134)\n 202.158.215.44 from 201.158.202.44 (202.158.215.62)\n Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001\n Last update: Tue Sep 22 12:25:17 2020\n\n 38561 2914 55433, (aggregated by 55433 202.68.67.135)\n 202.158.215.52 from 202.158.215.52 (202.158.215.62)\n Community: 7575:1002 7575:2462 7575:3002 7575:6001 7575:8001\n Last update: Mon Sep 21 06:44:58 2020\n"]
We can write the regular expression in free-spacing mode to make it self-documenting.
/
^mydummyhost\D+ # match 'mydummyhost' at the beginning of a line followed
# by 1+ characters other than digits (\D)
.+ # match 1+ characters, including line terminators
? # make previous match lazy (aka non-greedy)
(?= # begin a positive lookahead
\z # match end of string
| # or
^mydummyhost # match '^mydummyhost' at the beginning of a line
) # end positive lookahead
/mx # specify multiline (\m) and free-spacing regex definition modes
Multiline mode (named differently by other languages) causes the dot to match line terminators (\n and \r\n) as well as other characters.
You will see here that arr
contains a single element. The next step is to map arr
to values for each block. To simplify the presentation I will assume the string contains one block, namely, response
, but it should be evident how it could be generalized.
Extract the BGP ip
bgp_rgx = /^mydummyhost\D+\K\d{1,3}(?:\.\d{1,3}){3}$/
bgp_ip = response[bgp_rgx]
#=> "43.245.43.105"
See String#[]. The regex in free-spacing mode:
bgp_rgx =
/
^mydummyhost\D+ # match 'mydummyhost' at the begining of a line (^),
# followed by 1+ characters other than digits (\D)
\K # reset the beginning of the match to the current location
# and discard any previously-matched characters from the
# match that is returned
\d{1,3} # match 1-3 digits
(?:\.\d{1,3}) # match '.' followed by 1-3 characters, save to a
# non-capture group
{3} # execute the foregoing non-capture group 3 times
$ # match end of line
/x # specify free-spacing regex definition mode
Extract the path values
path_rgx = /(?<=^ {2})\d+(?: +\d+){2}(?=,)/
paths = response.scan(path_rgx)
#=> ["38561 2914 55432", "38561 2914 55433"]
In free-spacing mode:
path_rgx =
/
(?<=^[ ]{2}) # use a positive lookbehind (?<=...) to assert that the
# match that follows is preceded by two spaces at the
# beginning of a line
\d+ # match 1+ digits
(?:[ ]+\d+) # match 1+ spaces followed by 1+ digits, save to non-capture group
{2} # execute the foregoing non-capture group 2 times
(?=,) # use a positive lookahead (?=...) to assert that the
# preceding match is followed by ','
/x # specify free-spacing regex definition mode
Note that when writing regular expressions in free-spacing mode all spaces are removed before the expression is parsed. It is necessary, therefore, to protect all spaces that should not be stripped out. I've done that by putting a space character in a capture group ([ ]
). There are other ways to protect spaces, but that's not important.
Extract the originated values from the path values
originated = paths.map { |s| s[/\d+/] }
#=> ["38561", "38561"]
The regex reads, "match one or more digits".
Extract the community values
community_rgx = /^ {6}Community: +\K\d+:\d+(?: +\d+:\d+)+/
community = response.scan(community_rgx)
#=> ["7575:1002 7575:2462 7575:3002 7575:6001 7575:8001",
# "7575:1002 7575:2462 7575:3002 7575:6001 7575:8001"]
In free-spacing mode:
community_rgx =
/
^[ ]{6} # match 6 spaces at beginning of a line
Community:[ ]+ # match 'Community:' followed by 1+ spaces
\K # reset the beginning of the match to the current location
# and discard any previously-matched characters from the
\d+:\d+ # match 1+ digits, ':', 1+ digits
(?:[ ]+\d+:\d+) # match 1+ spaces, 1+ digits, ':', 1+ digits, save
# to a non-capture group
+ # execute the foregoing non-capture group 1+ times
/x # specify free-spacing regex definition mode
Combine values into a hash (optional)
params = {
bgp_ip: bgp_ip,
values: originated.zip(paths, community).map do |o,p,c|
{ originated: o, path: p, community: c }
end
}
#=> {:bgp_ip=>"43.245.43.105",
# :values=>[
# {:originated=>"38561", :path=>"38561 2914 55432",
# :community=>"7575:1002 7575:2462 7575:3002 7575:6001 7575:8001"},
# {:originated=>"38561", :path=>"38561 2914 55433",
# :community=>"7575:1002 7575:2462 7575:3002 7575:6001 7575:8001"}
# ]
# }
See Array#zip.
Again, if the string contains multiple blocks an array of hashes such as params
would be returned.
Upvotes: 2