# {} as record separators?

**URL:** <https://www.ruby-forum.com/t/as-record-separators/65614>\
**Category:** Ruby\
**Created:** [August 19, 2006, 8:17pm UTC](https://www.ruby-forum.com/t/as-record-separators/65614 "2006-08-19T20:17:07Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 19, 2006, 8:17pm UTC](https://www.ruby-forum.com/t/as-record-separators/65614/1 "2006-08-19T20:17:07Z")

</div>

I’m working on a ruby script to read Nagios’s status.dat and output it’s  
data.

The data format looks like this:

service {  
host\_name=[www.bob.com](http://www.bob.com)  
service\_description=bob-website  
modified\_attributes=0  
check\_command=check-bob-website  
event\_handler=  
has\_been\_checked=1  
should\_be\_scheduled=1  
check\_execution\_time=0.155  
check\_latency=0.250  
check\_type=0  
current\_state=0  
last\_hard\_state=0  
current\_attempt=1  
max\_attempts=3  
state\_type=1  
last\_state\_change=1155243779  
last\_hard\_state\_change=1155243779  
last\_time\_ok=1155507506  
last\_time\_warning=1154915720  
last\_time\_unknown=0  
last\_time\_critical=1155243478  
plugin\_output=HTTP OK HTTP/1.1 200 OK - 0.117 second response  
time  
performance\_data=time=0.116677s;;;0.000000 size=3998B;;;0  
last\_check=1155507506  
next\_check=1155507806  
current\_notification\_number=0  
last\_notification=0  
next\_notification=0  
no\_more\_notifications=0  
notifications\_enabled=1  
active\_checks\_enabled=1  
passive\_checks\_enabled=0  
event\_handler\_enabled=0  
problem\_has\_been\_acknowledged=0  
acknowledgement\_type=0  
flap\_detection\_enabled=0  
failure\_prediction\_enabled=1  
process\_performance\_data=1  
obsess\_over\_service=1  
last\_update=1155507620  
is\_flapping=0  
percent\_state\_change=0.00  
scheduled\_downtime\_depth=0  
}

What I’ve written works, but it only works for one block. I’m not quite  
sure how to make it work for the whole block?

Any advice would be greatly appreciated.

Here’s the code I’ve written:

Class server

def read\_nagiosstatus(filename)  
nagios\_status = {}  
for line in IO.readlines(filename):  
line.strip! # Remove all extraneous  
whitespace  
line.sub!(/#._$/, “”) # Remove comments  
next unless line.length \> 0 # check for end of file  
var, value = line.split(/\s_=\s\*/, 2)  
nagios\_status[var.intern] = value  
end  
return nagios\_status  
end

nagiosstatus = read\_nagiosstatus(“status.dat”)  
puts “Host name is #{nagiosstatus[:host\_name]}”  
puts “Service description is #{nagiosstatus[:service\_description]}”

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 20, 2006, 2:22am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/2 "2006-08-20T02:22:04Z")

</div>

Wow, this can’t be “The Ruby Way”, but it works:

def nagios\_data(data)  
blocks = data.strip.split(/._}\n(?=\w+\s+{)/)  
blocks.map do |nagios\_data|  
data\_points = nagios\_data.split("\n")  
# you can possibly use this as a hash key if they will be unique  
# block\_name = data\_points[0][/\w+/]  
data\_points[1…-1].inject({}) do |values, data\_point|  
unless data\_point =~ /\s+}/  
key, value = data\_point.strip.sub(/#._$/, “”).split(/\s\*=\s\*/, 2)  
values[key.intern] = value  
end  
values  
end  
end  
end

require ‘pp’

pp nagios\_data(File.read(“status.dat”))

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 20, 2006, 2:06am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/3 "2006-08-20T02:06:03Z")

</div>

\*\* first try \*\*

data = {}  
File.foreach( “nagios.dat” ) do |e|  
next if e =~ /service {|}/  
k,v = e.strip.split("=")  
data[k] = v  
end

it didnt work because of line below has more than one “=”

> ```
> performance_data=time=0.116677s;;;0.000000 size=3998B;;;0
> 
> ```

\*\* second try \*\*

data = {}  
File.foreach( “nagios.dat”) do |e|  
next if e =~ /service {|}/  
if e.strip =~ /(._?)=(._)/  
data[$1]=$2  
end  
end

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 20, 2006, 2:52am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/4 "2006-08-20T02:52:13Z")

</div>

> Here’s the code I’ve written:
> 
> Class server
> 
> def read\_nagiosstatus(filename)  
> nagios\_status = {}  
> for line in IO.readlines(filename):  
> line.strip! # Remove all extraneous  
> whitespace  
> line.sub!(/#._$/, “”) # Remove comments  
> next unless line.length \> 0 # check for end of file  
> var, value = line.split(/\s_=\s\*/, 2)  
> nagios\_status[var.intern] = value  
> end  
> return nagios\_status  
> end
> 
> nagiosstatus = read\_nagiosstatus(“status.dat”)  
> puts “Host name is #{nagiosstatus[:host\_name]}”  
> puts “Service description is #{nagiosstatus[:service\_description]}”

So this code actually works. It reads through my key=value pairs just  
fine. But of course, there’s no code here to separate between multiple  
instances of {}.

I understand now how to split on {} thanks to the examples you’ve all  
posted.

What I don’t understand though, is how would I address or list these?  
I’ve got two problems to solve.

The first is that I’d be happy if I could just get an object that was  
unique based on the host\_name and I could call  
nagios\_status[(:host\_name,:plugin\_output)] for each host in the  
status.dat file.

The second is that each host\_name has multiple services. My unique key  
needs to be based on the host\_name variable. And I think I need to end  
up so I have an object that’s like host\_name.service\_name.variables.

def read\_nagiosstatus(filename)  
nagios\_status = {}  
for line in IO.readlines(filename):  
line.strip! # Remove all extraneous  
whitespace  
line.sub!(/#._$/, “”) # Remove comments  
next unless line.length \> 0 # check for end of file  
var, value = line.split(/\s_=\s\*/, 2)  
nagios\_status[var.intern] = value  
end  
return nagios\_status  
end

nagiosstatus = read\_nagiosstatus(“status.dat”)  
puts #{nagios\_status[:host\_name]}

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 20, 2006, 2:29am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/5 "2006-08-20T02:29:42Z")

</div>

Scott wrote:

> Wow, this can’t be “The Ruby Way”, but it works:
> 
> def nagios\_data(data)  
> blocks = data.strip.split(/._}\n(?=\w+\s+{)/)  
> blocks.map do |nagios\_data|  
> data\_points = nagios\_data.split("\n")  
> # you can possibly use this as a hash key if they will be unique  
> # block\_name = data\_points[0][/\w+/]  
> data\_points[1…-1].inject({}) do |values, data\_point|  
> unless data\_point =~ /\s+}/  
> key, value = data\_point.strip.sub(/#._$/, “”).split(/\s\*=\s\*/, 2)  
> values[key.intern] = value  
> end  
> values  
> end  
> end  
> end
> 
> require ‘pp’
> 
> pp nagios\_data(File.read(“status.dat”))

When I try running this code, I get:

nagios\_test2.rb:10:in `nagios_data': undefined method`intern’ for  
nil:NilClass (NoMethodError)  
from nagios\_test2.rb:7:in `inject' from nagios_test2.rb:7:in`each’  
from nagios\_test2.rb:7:in `inject' from nagios_test2.rb:7:in`nagios\_data’  
from nagios\_test2.rb:3:in `map' from nagios_test2.rb:3:in`nagios\_data’  
from nagios\_test2.rb:19

Where does the intern method come from?

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 20, 2006, 3:00am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/6 "2006-08-20T03:00:06Z")

</div>

On Sat, 19 Aug 2006 14:17:11 -0400, Bob Norfolk  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> I’m working on a ruby script to read Nagios’s status.dat and output it’s  
> data.  
> Bob,

See if this works for you.

Regards,  
JJ

# 

# Copyright 2006, by John J., All Rights Reserved

# Released under the NewBSD license.

# 

require ‘ostruct’  
require ‘pp’

class Services \< Hash

SERVER\_RE = /^\s_service\s_{(.+?)\n\s\*}\s\*$/m  
VALUE\_RE = /^\s\*([^=]+)\s\*=\s\*([^\n=]_?)$/  
INTEGER\_RE = /^([-+]?\d+)$/  
FLOAT\_RE = /^([-+]?\d_.\d\*[eE]?\d\*)$/  
BOOLEAN\_TRUE\_RE = /^[1tTyY]+/ # e.g. 1, true, True, yes, Yes

# Types of declarations possible for field names.

:FIELD\_NAMES  
:FIELD\_REGULAR\_EXPRESSIONS

# 

# Conversion for field values.

# 

NAME\_TYPES = 0  
NAMES = 1  
CONVERSION\_BLOCK = 2  
CONVERSIONS = [

```
 # The following fields have time values that are the number
 # of seconds past the epoch.
 [
   # Types of names listed.
   [:FIELD_NAMES],
   # Field name declarations.
   ['last_state_change', 'last_hard_state_change',
   'last_time_ok', 'last_time_warning', 'last_time_unknown',
   'last_time_critical', 'last_check', 'next_check',
   'last_notification', 'next_notification', 'last_update'],
   # Code to convert from a String to whatever this is.
   lambda { |seconds_string|
     return Time.at(seconds_string.to_i)
   }
 ],

 # The following appear to be boolean fields.
 [
   [:FIELD_NAMES, :FIELD_REGULAR_EXPRESSIONS],
   ['modified_attributes', 'has_been_checked', 

```

‘should\_be\_scheduled’,  
‘no\_more\_notifications’,  
/\w+\_enabled$/, # all field names ending in \_enabled  
‘problem\_has\_been\_acknowledged’, ‘process\_performance\_data’,  
‘obsess\_over\_service’, ‘is\_flapping’],  
lambda { |boolean\_string|  
if boolean\_string =~ BOOLEAN\_TRUE\_RE  
true  
else  
false  
end  
}  
]  
]

HOST\_NAME\_PROPERTY = ‘host\_name’

def convert\_field(name, value)  
CONVERSIONS.each { |conv\_set|  
name\_types = conv\_set[NAME\_TYPES]  
names = conv\_set[NAMES]  
conversion\_block = conv\_set[CONVERSION\_BLOCK]

```
   if name_types.include?(:FIELD_REGULAR_EXPRESSIONS)
     names.each { |name_dec|
       case
       when name_dec.class==String
         if name == name_dec
           return conversion_block.call(value)
         end
       when name_dec.class==Regexp
         if name =~ name_dec
           return conversion_block.call(value)
         end
       else
         fail "Don't know what a #{name_dec.class} field name is"
       end
     }
   end
   if name_types.include?(:FIELD_NAMES)
     if names.include?(name)
       return conversion_block.call(value)
     end
   end
 }

 # Try implicit conversions.
 case value
 when INTEGER_RE
   return Integer(value)
 when FLOAT_RE
   return Float(value)
 end

 return String(value)

```

end

def initialize(filename)  
super  
contents = File.open(filename).read  
contents.scan(SERVER\_RE) { |value\_ary|  
values = value\_ary[0]  
host\_name = “”  
service = OpenStruct.new  
values.scan(VALUE\_RE) { |name, value|  
converted = convert\_field(name, value)  
dumped = Marshal.dump(converted)  
service.instance\_eval(  
“self.#{name} = Marshal.load(‘#{dumped}’)”)  
host\_name=value if name == HOST\_NAME\_PROPERTY  
}  
self[host\_name] = service  
}  
end  
end

services = Services.new(‘nagio.txt’)  
services.each\_pair{ |service, properties|  
puts “Host: #{service}”  
puts “\t has\_been\_checked: #{properties.has\_been\_checked}”  
puts “\t next\_check: #{properties.next\_check}”  
puts “\tnotifications\_enabled: #{properties.notifications\_enabled}”  
}

if services[‘[www.bob.com](http://www.bob.com)’].obsess\_over\_service  
puts “Someone is obsessing over Bob!”  
else  
puts “No one is obsessing over Bob.”  
end

if services[‘[www.test.com](http://www.test.com)’].obsess\_over\_service  
puts “Someone is obsessing over Test!”  
else  
puts “No one is obsessing over Test.”  
end

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 22, 2006, 10:00pm UTC](https://www.ruby-forum.com/t/as-record-separators/65614/7 "2006-08-22T22:00:35Z")

</div>

John J. wrote:

> On Sat, 19 Aug 2006 20:59:15 -0400, John J. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
> wrote:
> 
> > JJ
> 
> So, did it work?

Actually, I was just working on debugging this error that the script  
above gives :

nagiostest3.rb:111:in `initialize': (eval):1:in `initialize’: compile  
error (SyntaxError)  
(eval):1: unterminated string meets end of file  
(eval):1: parse error, unexpected $, expecting ‘)’  
Ã­elf.last\_time\_critical = Marshal.load(u: Time  
^  
from nagiostest3.rb:108:in `initialize' from nagiostest3.rb:104:in `initialize’  
from nagiostest3.rb:119

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 22, 2006, 6:46pm UTC](https://www.ruby-forum.com/t/as-record-separators/65614/8 "2006-08-22T18:46:51Z")

</div>

On Sat, 19 Aug 2006 20:59:15 -0400, John J. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
wrote:

> JJ

So, did it work?

---

<div class="post-metadata">

**Author:** ![punkrockgeekboy](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/e495f1/32.png) [@punkrockgeekboy](https://www.ruby-forum.com/u/punkrockgeekboy)\
**Post date:** [August 23, 2006, 4:30am UTC](https://www.ruby-forum.com/t/as-record-separators/65614/9 "2006-08-23T04:30:26Z")

</div>

On Tue, 22 Aug 2006 16:00:37 -0400, Bob Norfolk  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> above gives :  
> from nagiostest3.rb:119

Hm, probably a ’ in the dumped data.  
Try this instead:

def initialize(filename)  
super  
contents = File.open(filename).read  
contents.scan(SERVER\_RE) { |value\_ary|  
values = value\_ary[0]  
host\_name = “”  
service = OpenStruct.new  
values.scan(VALUE\_RE) { |name, value|  
converted = convert\_field(name, value)  
dumped = Marshal.dump(converted)  
dumped = dumped.unpack(“H\*”)  
service.instance\_eval(  
“self.#{name} =  
Marshal.load([‘#{dumped}’].pack(‘H\*’))”)  
host\_name=value if name == HOST\_NAME\_PROPERTY  
}  
self[host\_name] = service  
}  
end  
end

It converts the dumped data to a hex string, then back, eliminating the  
need for escaping characters, etc.

Regards,  
JJ
