Replace strings in a file with ruby - ruby

I'm trying to create a program that reads ps and outputs the pid and commandline, but if the process was started by the kernel it should return a blank line.
require 'fileutils'
procs=`ps -eo pid,cmd`
o = File.open("proc","w")
f = o.write("proc")
o.close
f_in = File.open('proc', 'r')
f_out = File.open('procs', 'w')
replace = ""
f_in.each do |line|
if line =~ (/\s*\[(\w+)\]\$/)
f_out << "\n"
else
f_out << line
end
end
f_out.write("procs")
f_in.close
f_out.close
FileUtils.mv "procs", ["proc", Time.now.strftime("%Y-%m-%d")].join(".")
ps -eo pid,cmd like:
PID CMD
1 /sbin/init
2 [migration/0]
3 [ksoftirqd/0]
4 [watchdog/0]
5 [events/0]
6 [khelper]
7 [kthread]
8 [xenwatch]
9 [xenbus]
17 [kblockd/0]
I want to remove all of the lines in brackets but keep the PID like this:
PID CMD
1 /sbin/init
2
3
4
5
6
7
8
9
17

This looks like it will do it:
File.open("proc.#{ Time.now.strftime('%F') }", 'w') do |fo|
fo.puts `ps -eo pid,cmd`.lines.map{ |li| li[ /^([^\[]+)/, 1] }
end
li[ /^([^\[]+)/, 1] means "capture everything from the start of the line that isn't a '[' and return it.
It created a file called "proc.2011-04-16" which looks like:
PID CMD
1 /sbin/init
2
3
4
5
[...]
255 upstart-udev-bridge --daemon
296 rsyslogd -c4
303 dbus-daemon --system --fork
315 udevd --daemon
398 avahi-daemon: running
443 avahi-daemon: chroot helper
493
[...]
EDIT: There were a couple things I thought could be more succinct:
File.open('proc.' + Date.today.strftime, 'w') do |fo|
fo.puts `ps -eo pid,cmd`.gsub( /\s+\[.+?\]$/, '')
end

Just do
string.gsub(/\[.*?\]/, '')
or
string.gsub(/\[[^\[\]]*\]/, '')

Related

Non-blocking backticks

I want to run multiple time-consuming shell commands from Ruby in a non-blocking (asynchronous) way.
I want to pass options to commands, receive output in Ruby, and (ideally) handle errors.
The script below will naturally take 15 seconds to execute:
test.rb
3.times do |i|
puts `sleep 5; echo #{i} | tail -n 1` # some time-consuming complex command
end
$ /usr/bin/time ruby test.rb
0
1
2
15.29 real 0.13 user 0.09 sys
With Thread, it can apparently be executed in parallel, and it takes only 5 seconds, as expected:
threads = []
3.times do |i|
threads << Thread.new {
puts `sleep 5; echo #{i} | tail -n 1`
}
end
threads.each {|t| t.join() }
$ /usr/bin/time ruby test.rb
2
0
1
5.17 real 0.12 user 0.06 sys
But is this the best approach? Is there any other way?
I have also written using Open3.popen2, but this seems to take 15 seconds to execute as in the first example (unless wrapped in a Thread):
require 'open3'
3.times do |i|
Open3.popen2("sleep 5; echo #{i} | tail -n 1") do |stdin, stdout|
puts stdout.read()
end
end
The documentation describes "block form" and "non-block form", but this "block" refers to anonymous functions, and has nothing to do with concurrency, correct?
Is the Open3 class alone only capable of blocking execution?
The problem with your code is that stdout.read is a blocking call.
You could defer the reading until the command is finished.
At first, create the commands:
commands = Array.new(3) { |i| Open3.popen2("sleep 5; echo hello from #{i}") }
Then, wait for each command to finish:
commands.each { |stdin, stdout, wait_thr| wait_thr.join }
Finally, gather the output and close the IO streams:
commands.each do |stdin, stdout, wait_thr|
puts stdout.read
stdin.close
stdout.close
end
Output: (after 5 seconds)
hello from 0
hello from 1
hello from 2

Graphing multiple disk volumes in Nagiosgraph

I want to graph free disk space for all /dev/ volumes on a server in a single nagiosgraph.I have been following a tutorial here
This tutorial uses a Ruby script to check the entire disk structure using this code:
used_space=`df -h / | grep -v "Filesystem" | awk '{print $5}'`
I have two questions, how to best dynamically determine which volumes a server has and then how to output the free space for each volume to nagios perfdata in a way that I can get a line for each volume on the server.
Here is my complete script - thanks for the answer below:
#!/usr/bin/env ruby
def largest_hash_key(hash)
hash.max_by{|k,v| v}
end
filesystem = %x(df -h)
perfdata = filesystem.split("\n")
.grep(/\A\/dev/)
.map(&:split)
.map{ |e| "'%s'=%s" % [ e[-1], e[-2] ] }
.join(" ")
volumes = Hash[perfdata.split(" ").map {|str| str.split("=")}]
volumes = volumes.map{ |k, v| [k, v.to_i] }
full_disk = largest_hash_key(volumes)
pc_full = full_disk[1]
message = "#{perfdata} | #{perfdata}"
if pc_full > 94
puts "DISK CRITICAL - #{message}"
exit 2
elsif pc_full > 89
puts "DISK WARNING - #{message}"
exit 1
else
puts "DISK OK - #{message}"
exit 0
end
UPDATE: joining with spaces instead of new lines, see markhorrocks comment below.
Assuming your script is running on a Linux machine and this is the format you are referring to by nagios perfdata, you could write:
%x(df -h)
.split("\n")
.grep(/\A\/dev/)
.map(&:split)
.map{ |e| "'%s'=%s" % [ e[0], e[-2] ] }
.join(" ")
which will output
'/dev/sda3'=50%

mixing file.write and system calls that write to same file

I have code that invokes two system commands that write to a file and I need to add something else from my code in between these calls:
File.open('test.txt', 'w') {|f|
`echo 1 > #{f.path}`
f.write '2'
`echo 3 >> #{f.path}`
}
as the result the file contains just
2
3
the first line is missing. I am sure there is a simple solution, but I cannot find it.
You are opening the file in "write" mode which is clobbering the first echo. Instead use 'append' mode. Additionally, you're not flushing after you write "2" so it'll be out of order when you read it back. Remember, f.write doesn't append a newline, so you probably need that too.
irb(main):020:0> File.open('asdf', 'a') do |f|
irb(main):021:1* `echo 1 > asdf`
irb(main):022:1> f.write("2\n") and f.flush
irb(main):023:1> `echo 3 >> asdf`
irb(main):024:1> end
=> ""
irb(main):025:0> File.read('asdf')
=> "1\n2\n3\n"
irb(main):026:0> puts File.read('asdf')
1
2
3
The File.open(name, 'a') is the important part. It means append to this file instead of overwriting it. See http://www.ruby-doc.org/core-2.0/IO.html#method-c-new and What are the Ruby File.open modes and options? for descriptions of file open modes.
If it's important to delete any existing file, the first echo will implicitly take care of that (since it's a single >). Or you can do it in ruby explicitly:
File.delete('asdf') if File.exists?('asdf')
to answer my own question:
File.open('test.txt', 'w') {|f|
`echo 11 > #{f.path}`
f.seek(0, IO::SEEK_END)
f.write "2\n"
f.flush
`echo 33 >> #{f.path}`
}

How do I get the exit status for a command invoked with IO.popen?

I am using IO.popen to execute a command and am capturing the output like so:
process = IO.popen("sudo -u service_user -i start_service.sh") do |io|
while line = io.gets
line.chomp!
process_log_line(line)
end
end
How can I capture the exit status of *start_service.sh*?
You can capture the exit status of a command invoked via IO.open() by referencing $? as long as you have closed the pipe at the end of your block.
In the example above, you would do:
process = IO.popen("sudo -u service_user -i start_service.sh") do |io|
while line = io.gets
line.chomp!
process_log_line(line)
end
io.close
do_more_stuff if $?.to_i == 0
end
See Ruby Core Library entry for IO.popen for more information.

File reading problem

f = File.open("/test/serverlist.txt", "r")
list = f.readlines
list.each do|servers|
File.open('/test/results.txt','w') do |b|
servers.each do |p|
r = `ping -n 1 #{p}`
b.puts r
end
end
It reads the serverlist file, and returns a string. The serverlist file contains the following IP addresses:
192.168.150.254
192.168.120.2
Are you looking to read each line from the file and then do something with like this.
fout = File.open('/test/results.txt','w')
File.open("/test/serverlist.txt", "r").each_line do |server|
server.chomp!
r = `ping -n 1 #{server}`
fout.puts r
end
I don't think you will need to iterate over the server line itself, and with a few style mods added and ping(1) arguments changed, I would suggest...
open 'serverlist.txt', 'r' do |f|
open '/tmp/results.txt', 'w' do |b|
f.readlines.each do |server|
b.puts `ping -c 1 -t 1 #{server}`
end
end
end
Just use b.write in place of b.puts
if you're using linux you could just go for
File.open("serverlist.txt").each { |addy| `echo "#{`ping -c 1 #{addy}`}" >> result.txt` }
and be done with it
well .. maybe add
`echo "# server-availability" > result.txt`
before the above line so the file gets reset every time you call this

Resources