The three rules of Ruby Quiz 2:
1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.
2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Quiz 2. Until then,
please visit the temporary website at
<http://splatbang.com/rubyquiz/>.
3. Enjoy!
Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion. Please reply to
the original quiz message, if you can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
## Uptime Since... (#174)
Nice and easy one this week. Your task is to write a Ruby script that
reports the date and time of your last reboot, making use of the
`uptime` command.
While `uptime` is available on the various Unixes and Mac OS X,
Windows users might need to do a little extra work. Here are [two][1]
[options][2] for Windows users.
[1]: http://support.microsoft.com/kb/555737
[2]: http://articles.techrepublic.com.com/5100-10878_11-5826014.html
--
Matthew Moss <matthe...@gmail.com>
require 'date'
(now, _, dd, _, _, dhm) = `uptime`.split(/ +|,/)
(dd, dh, dm) = dd.to_i, *dhm.split(':').map { |x| x.to_i }
dh += (dm / 60.0)
dd += (dh / 24.0)
last = DateTime.parse(now) - dd
puts "Last reboot: #{last.year} #{Date::ABBR_MONTHNAMES[last.mon]}
#{last.day} at #{last.hour}:#{last.min}"
# A windows solution.
require 'rubygems'
require 'ruby-wmi'
require 'date'
str = WMI::Win32_OperatingSystem.find(:first).LastBootupTime
boot = DateTime.strptime(str, "%Y%m%d%H%M")
boot_date = boot.strftime("%Y %b")
boot_time = boot.strftime("%I:%M")
puts "Last reboot: #{boot_date} at #{boot_time}"
coeff = [60,3600]; i = 0; offset = 0
s = %x{uptime}
coeff.push 86400 if s =~ /day/
s.scan(/\d+/) do #I don't know how to stop scanning when I've had
enough ^^;;
|text|
i = i + 1
offset = offset + text.to_i*(coeff.pop||0) if (3..5) === i
end
puts (Time.now - offset).strftime("System booted at %H:%M of %A, %B %d
(%Y)")
(I don't know anything about regexp, and it shows)
--
Posted via http://www.ruby-forum.com/.
====
SecsPerMinute = 60
SecsPerHour = 60*SecsPerMinute
SecsPerDay = 24*SecsPerHour
match = /up (\d+) days, (\d+):(\d+),/.match %x{uptime}
# extract data from reg. exp. and convert to integers
days, hours, minutes = *((1..3).map { |index| match[index].to_i })
now = Time.now
now -= now.sec # normalize to 0 seconds into the current minute
boot_time = now - days*SecsPerDay - hours*SecsPerHour -
minutes*SecsPerMinute
boot_time_s = boot_time.strftime("%a %b %e %l:%M %p")
puts "Machine last booted: #{boot_time_s} (+/- 1 minute)."
====
Eric
====
On-site, hands-on Ruby and Ruby on Rails training is
available from http://LearnRuby.com !
if `uptime`.include? "day"
puts(Time.now - ((`uptime`.split[2].to_i * 1440) + (`uptime`.split[4].split(":")[0].to_i * 60) + (`uptime`.split[4].split(":")[1].to_i)) * 60)
elsif `uptime`.include? "min"
puts(Time.now - `uptime`.split[2].split(":")[0].to_i * 60)
else
puts(Time.now - (`uptime`.split[2].split(":")[0].to_i * 60) + (`uptime`.split[2].split(":")[1].to_i))
end
--
nathan
nathan_at_nathanpowell_dot_org
To give anything less than your best is to sacrifice the gift.
~ Steve Prefontaine
------------------------------------
My solution, tested in Ubuntu 8.04 and Fedora Core 2:
uptime = (`uptime`.match /up (.*),.*user/)[1].delete(" ")
captures = (uptime.match /((\d+)days,)?(\d+):(\d+)/).captures[1..-1]
elapsed_seconds = captures.zip([86440, 3600, 60]).inject(0) do |total, (x,y)|
total + (x.nil? ? 0 : x.to_i * y)
end
puts "Last reboot was on #{Time.now - elapsed_seconds}"
Nice quiz !!!
Jesus.
puts Time.now - File.read('/proc/uptime').match(/^(\d+\.\d+) /)[1].to_f
--
Jesse Merriman
On Aug 23, 8:35 am, "Matthew Moss" <matthew.m...@gmail.com> wrote:
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>
> The three rules of Ruby Quiz 2:
>
> 1. Please do not post any solutions or spoiler discussion for this
> quiz until 48 hours have passed from the time on this message.
>
> 2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
> permanent, new website is in the works for Ruby Quiz 2. Until then,
> please visit the temporary website at
>
> <http://splatbang.com/rubyquiz/>.
>
> 3. Enjoy!
>
> Suggestion: A [QUIZ] in the subject of emails about the problem
> helps everyone on Ruby Talk follow the discussion. Please reply to
> the original quiz message, if you can.
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>
> ## Uptime Since... (#174)
>
> Nice and easy one this week. Your task is to write a Ruby script that
> reports the date and time of your last reboot, making use of the
> `uptime` command.
require 'sys/uptime'
p Sys::Uptime.uptime
>:)
Regards,
Dan
PS - No parsing involved. Uses sysctl/times/wmi, depending on platform.
> Nice and easy one this week. Your task is to write a Ruby script that
> reports the date and time of your last reboot, making use of the
> `uptime` command.
>
> While `uptime` is available on the various Unixes and Mac OS X,
> Windows users might need to do a little extra work. Here are [two][1]
> [options][2] for Windows users.
last | grep reboot | head -1
Am I doing it right? :P
-Erik
This works for me on OS X.
require 'time'
Time.parse `last | tail -1`
Michael Guterl
Why use `uptime` when there's `who -b`?
Here is my solution. It should run out of the box (i.e. no gems needed)
on all major platforms.
---
require 'time'
methods = [
lambda { # Windows
require 'Win32API'
getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L')
Time.now - getTickCount.call() / 1000.0
},
lambda { # *BSD, including Mac OS
Time.at(`sysctl -b kern.boottime 2>/dev/null`.unpack('L').first)
},
lambda { # Some other form of *nix
Time.parse(`who -b 2>/dev/null`)
}
]
begin
unless methods.empty?
boot_time = methods.shift.call
else
puts "Unable to determine time of last reboot. Sorry!"
exit!(1)
end
rescue Exception
retry
end
puts "Last reboot: #{boot_time.asctime}"
---
Regards,
Matthias
I realized that there was a case I wasn't taking into account: a machine booted
less than 1 hour ago. So here is my revised solution, also compacting a little
bit things, using only one regexp:
captures = (`uptime`.match /up (?:(?:(\d+) days,)?\s+(\d+):(\d+)|(\d+)
min)/).captures
elapsed_seconds = captures.zip([86440, 3600, 60, 60]).inject(0) do
|total, (x,y)|
total + (x.nil? ? 0 : x.to_i * y)
end
puts "Last reboot was on #{Time.now - elapsed_seconds}"
Anybody knows what's the format for a machine that's been up more than
a day, but less than two?
Is it "up 1 day" or "up 1 days"? Anyway I'm thinking changing that
part of the regexp to "day(?:s)?"
should suffice to take into account both cases, but I can't test it right now.
Jesus.
> I realized that there was a case I wasn't taking into account: a machine
> booted
> less than 1 hour ago.
And I missed the case of an uptime between 24 and 25 hours. (On OSX, at
least). Which makes my solution fail quite badly in that time bracket.
A solution tested with windows uptime.exe, FreeBSD and Linux. It should
work with most of the others and in most of the edge cases, I think.
#! /usr/local/bin/ruby
def startup(t)
u = (t.downcase.match(/up(.*?)(?:\d+ user.*)?$/)[1])
lu = u.split(/([0-9:]+)/).map { |e| e.strip.gsub(/,/, '') }.reject { |e| e == '' }
count = 0
lu.each_index do |i|
count += case lu[i]
when /(\d+):(\d+)(?::(\d+))?/
$1.to_i * 3600 + $2.to_i * 60 + ($3 ? $3.to_i : 0)
when /(\d+)/
n = lu[i].to_i
case lu[i + 1]
when nil then raise "Unknown format"
when /^day/ then n * 86400
when /^hour/ then n * 3600
when /^min/ then n * 60
when /^sec/ then n
else raise "Unknown modifier (#{lu[i + 1]})"
end
else
0
end
end
Time.now - count
end
[ `uptime`,
'11:40:08 up 15 days, 16:08, 1 user,2 load average: 0.06, 0.04, 0.01',
'\\LAPHROAIG has been up for: 25 day(s), 13 hour(s), 46 minute(s), 10 second(s)'
].each { |c| puts startup(c).strftime('%c') }
12:09 fred@balvenie:~/ruby> ./uptime.rb
Wed Jun 25 07:05:41 2008
Sun Aug 10 20:01:41 2008
Thu Jul 31 22:23:31 2008
Fred
--
Tried to save a place from the cuts and the scratches
Tried to overcome the complications and the catches
Nothing ever grows and the sun doesn't shine all day (Nine Inch Nails,
Tried to save myself but myself keeps slipping away Into the Void)
On the machines I know (several *nixes) it is neither "up 1 day" nor "up
1 days", but "up 1 day(s)". To make matters worse some versions of
`uptime` yield a localized message:
---
1:49pm in Betrieb 6 Tag(e), 9:12, 12 Benutzer, Durchschnittslast:
3,11, 3,13, 3,19
---
Maybe a more general approach is needed to account for these corner
cases.
On Aug 25, 11:20 pm, Matthias Reitinger <reiti...@in.tum.de> wrote:
> Matthew Moss wrote:
> > Nice and easy one this week. Your task is to write a Ruby script that
> > reports the date and time of your last reboot, making use of the
> > `uptime` command.
>
> Why use `uptime` when there's `who -b`?
Because `uptime` is what I requested... :D
Sometimes it's not about the final result, but about the process.
Yep, this is a "count backwards in time" exercise slightly obfuscated.
My "solution" was a bit of a joke, and I apologize for the mess I've
caused.
I know, I know. I guess I just wanted to make my life a bit easier
there :)
On Aug 26, 10:44 am, Matthias Reitinger <reiti...@in.tum.de> wrote:
> Matthew Moss wrote:
> > On Aug 25, 11:20 pm, Matthias Reitinger <reiti...@in.tum.de> wrote:
> >> Matthew Moss wrote:
> >> > Nice and easy one this week. Your task is to write a Ruby script that
> >> > reports the date and time of your last reboot, making use of the
> >> > `uptime` command.
>
> >> Why use `uptime` when there's `who -b`?
>
> > Because `uptime` is what I requested... :D
>
> > Sometimes it's not about the final result, but about the process.
>
> I know, I know. I guess I just wanted to make my life a bit easier
> there :)
No worries. I'm not deducting any points this week. ;)
-Adam
##########################
## Windows-uptime.rb
## Ruby Quiz #
## -Adam Shelly
## (The hard way)
#find out what form windows is going to report the date in
dateform = `reg query "HKCU\\Control Panel\\International" /v sShortDate`.chomp
dateform = /REG_SZ\t(.*)$/m.match(dateform)[1].chomp
timeform = `reg query "HKCU\\Control Panel\\International" /v
sTimeFormat`.chomp
timeform = /REG_SZ\t(.*)$/m.match(timeform)[1].chomp
timeform.gsub!(/:[sS]+/,'')
is_PM = `reg query "HKCU\\Control Panel\\International" /v s2359`.chomp
is_PM = /REG_SZ\t(.*)$/m.match(is_PM)[1].chomp
#these are the format characters it may use (sorted in descending
order by size: year -> minute)
$special="yYmMdDhHmMtT"
#build a regexp that matches the format string,
# record which order the result groups are returned in
# Assumes that there is sone separator character
# won't work if the format is something like DDMMYY
def buildQuery formatstr, indexes
idx=1
formatstr.each_byte{|b|
if $special.include?(b.chr)
indexes[b.chr.upcase]=idx
else
idx+=1
end
}
#replace all the format chars with word matchers
f = formatstr.gsub(Regexp.new("[#{$special}]"),'\w')
#make groups
f = f.gsub(/(\\w)+/,'(\w+)')
return f
end
#storage for regexp group indexes
dateidx,timeidx={},{}
#build regexp to match result
regexp = Regexp.new "since "+buildQuery(dateform, dateidx)+'
'+buildQuery(timeform, timeidx)
#since we are combining 2 regexps, the second set of groups are offset
by the number of groups in the first
timeidx.each_key{|k| timeidx[k]+=dateidx.values.max}
#do the match
datematch = regexp.match(`net stats srv`)
a=[]
idxset=dateidx
#build an array with the results of the match, in descending order,
$special.upcase.squeeze.each_byte{|b|
idxset = timeidx if b==?H #switch to time indexes when we get to Hours
a<<datematch[idxset[b.chr]]
}
#add 12 if string contains this locale's version of 'PM'
a[3]=a[3].to_i+12 if datematch[timeidx['T']]==is_PM
#convert month names to integers, if needed
# ??Question: Does strftime return names in the current locale??
(1..12).each{|m|
a[1]=m if datematch[dateidx['M']].include?(Time.utc(0,m).strftime("%b"))
}
#construct the start time, calculate seconds elapsed until now
starttime = Time.local(*a)
s=(Time.now-starttime).to_i;
#show results
print datematch[0].gsub("since", "Last reboot at")
puts ".\n #{s} seconds ago."
#expand seconds into normalized units. (Is there a library function
to do this?)
puts 'uptime = '+
[['%d seconds',60],['%d minutes',60],['%d hours',24],['%d
days',365],['%d years',1000]].map{|w,v|
s,rem=s.divmod v;
w%rem if rem>0
}.compact.reverse*' '
####################################
## Now, the Easy way
require 'Time'
datematch = /since (.*)$/.match(`net stats srv`)
s= Time.now - Time.parse(datematch[1])
print "\n"+datematch[0].gsub("since", "Last reboot at")
puts ".\n #{s.to_i} seconds ago."
puts "uptime = %.2f days."%[s/(60*60*24.0)]
I installed rb-sys-uptime on Mac OS X, couldn't get this to work. It
recognizes Sys::Uptime, but when I try to call method 'uptime', I get:
"Sys::UptimeError: failed to extract uptime"
That doesn't come close to working for me on OSX. There are many more
timestamps in that file than just the reboots. The version Erik
provided works correctly.
FWIW, `last | grep reboot` returns nothing for me on my MacBook.
Also, you're right, my version doesn't seem to be working right either...
There are a number of ways to attack this problem, as shown in the
variety of the solutions. First, let's look at "the right way" to
solve this problem: use a library or module that does the work for
you. Such was _Daniel Berger_'s solution, shown here:
require 'sys/uptime'
p Sys::Uptime.uptime
Unfortunately, this didn't work on my machine, and hopefully that's
only because of an outdated version of rb-sys-uptime, or some similar
reason. I'll take Daniel's word that this works on his, though I
wonder whether he's returning the uptime or the time of last reboot
(as requested). Still, with such a module that abstracts the platform
differences, this is an easy win... if you get it to work. (Offhand,
this seems to be a Darwin, i.e. Mac OS X, module; if true, then it's
not "the right way" for other platforms.)
Let's go on to the submission from _Erik Hollensbe_, which isn't even Ruby code.
last | grep reboot | head -1
Sometimes the Unix way is the best way. Of course, you need to know
where this information resides, and there may still be platform
differences (along with significant command-line differences on
Windows), but this is a simple and quick one-liner that requires only
a few common tools.
Let's look now at some Ruby code. We'll look first at _Jesus
Gabriel_'s second submission:
captures = (`uptime`.match /up (?:(?:(\d+)
days,)?\s+(\d+):(\d+)|(\d+) min)/).captures
elapsed_seconds = captures.zip([86440, 3600, 60, 60]).inject(0) do
|total, (x,y)|
total + (x.nil? ? 0 : x.to_i * y)
end
puts "Last reboot was on #{Time.now - elapsed_seconds}"
The uptime information is gathered from the call to `uptime`. Note the
backticks, which indicate that this is a shell command to be executed,
and its output returned. The output is matched against a regular
expression, containing a number of groups, several of which are
optional. Four of those groups, however, are returned, to match
against days, hours or minutes passed. (The minutes may be grouped in
one of two ways.)
The captured results are paired up with the array `[86440, 3600, 60,
60]`, each entry corresponding to the number of seconds in a day, hour
or minute. Finally, using inject, the total number of seconds since
the last reboot is determined. Subtracting this from `Time.now`
results in the time of the last reboot.
A few comments... As was mentioned on the mailing list, the regular
expression to match the output of `uptime` is fragile. A few
variations were shown to exist. A more complex regular expression
might be able to capture more variants, though the better answer is
not to call `uptime` as a shell command, but rather use the
appropriate system services to access the information directly.
However, all this parsing is a direct result of my asking for it, so
for this quiz, I'm not too concerned about this problem.
What I found a bit interesting (or confusing) was the different
classes available for date/time information: `Date`, `Time` and
`DateTime`. My own solution used `DateTime`, which I wrongly assumed I
would need (thinking `Time` was only time information). I should have
explored more, since the `Time` solution seems simpler.
Additionally, one subtracts _days_ from `DateTime` objects, but
subtracts _seconds_ from `Time` objects. `Time` supports a `now`
method, while `DateTime` does not. I expect there is some amount of
logic to these classes, but it seems to have escaped me this time
around.
One last comment on Jesus' solution. The `uptime` shell command
provides the current time simultaneously with the elapsed time since
last reboot. Yet his solution uses `Time.now` rather than the provide
time. For this quiz, it's not big thing: the answer would, at most, be
off by one minute. Still, it may be an important consideration for
other scripts to preserve the matching time, to ensure accuracy.
Make sure to take a look at the other solutions. In particular, the
Windows solution from _Gordon Thiesfeld_ which makes use of an
operating system module to get the last reboot time directly. Also,
from _Jesse Merriman_ is a solution that access the process file
system.
--
Matthew Moss <matthe...@gmail.com>