Ruby check if mounted inside an if condition - ruby

contents of a.rb is :
if system("mount | grep /boot") != ""
print "true" # or do something
else
print "false" # or do something else
end
Running this code doesn't print "true" or "false", but prints the output of system call.
]$ ruby a.rb
/dev/sda1 on /boot type ext4 (rw)
What's the right syntax to check the if condition ?

if `mount | grep /boot` != ""
print "true" # or do something
else
print "false" # or do something else
end

In ruby, backticks cause the process to fork, which is expensive and slow. Instead, you could read /etc/mtab and look at the content which prevents forking.
if File.read('/etc/mtab').lines.grep(/boot/)[0]
# not nil, its mounted
else
# its nil, not mounted
end

Don't use system calls, instead you can try this (fromFile path depends of
your operating system, in RedHat is /proc/mounts):
fromFile='/proc/mounts'
toFind='/boot'
if File.readlines(fromFile).grep(toFind).size > 0
print "true" # or do something
else
print "false" # or do something else
end

Related

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%

File.exist? always returns false even when file does exist

I have a program that tries to open a file:
Dir.chdir(File.dirname(__FILE__))
puts "Enter file name: ";
relPath = gets;
absPath = Dir.pwd << "/" << relPath;
if File.exist?(absPath) then
puts "File exists";
file = File.open(absPath, "r");
other code...
else
puts "File does not exist";
end
It always prints "File does not exist" even when the current directory exists and the file also exists. The file and script are in the same directory.
I am running it on Mac OS X Yosemite (10.10.3) and Ruby 2.2.0p0.
I can't explain why (albeit I have strong belief that it's for some whitespace characters) but with this little contribution it works ok.
Dir.chdir(File.dirname(__FILE__))
print "Enter file name:";
relPath = gets.chomp; #intuitively used this, and it wroked fine
absPath = File.expand_path(relPath) #used builtin function expand_path instead of string concatenation
puts absPath
puts File.file?(absPath)
if File.exist?(absPath) then
puts "File exists";
puts File.ctime(absPath) #attempting a dummy operation :)
else
puts "File does not exist";
end
runnning code
$ ls -a anal*
analyzer.rb
$ ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]
ziya#ziya:~/Desktop/code/ruby$ ruby fileexists.rb
Enter file name:analyzer.rb
/home/ziya/Desktop/code/ruby/analyzer.rb #as a result of puts absPath
true #File.file?(absPath) => true
File exists
2015-06-11 12:48:31 +0500
That code has syntax error ("if" doesnt need "then"), and you dont have to put ";" after each line.
try
Dir.chdir(File.dirname(__FILE__))
puts "Enter file name: "
relPath = gets
absPath = "#{Dir.pwd}/#{relPath.chop}"
if File.exist?(absPath)
puts "File exists"
file = File.open(absPath, "r")
else
puts "File does not exist"
end
remember that gets will add a new line character so you will need to do a chomp, and that way to concatenate string won't work on ruby.
Your code is not idiomatic Ruby. I'd write it something like this untested code:
Dir.chdir(File.dirname(__FILE__))
puts 'Enter file name: '
rel_path = gets.chomp
abs_path = File.absolute_path(rel_path)
if File.exist?(abs_path)
puts 'File exists'
File.foreach(abs_path) do |line|
# process the line
end
else
puts 'File does not exist'
end
While Ruby supports the use of ;, they're for use when we absolutely must provide multiple commands on one line. The ONLY time I can think of needing that is when using Ruby to execute single-line commands at the command-line. In normal scripts I've never needed ; between statements.
then is used with if when we're using a single line if expression, however, we have trailing if which removes the need for then. For instance, these accomplish the same thing but the second is idiomatic, shorter, less verbose and easier to read:
if true then a = 1 end
a = 1 if true
See "What is the difference between "if" statements with "then" at the end?" for more information.
Instead of relPath and absPath we use snake_case for variables, so use rel_path and abs_path. It_is_a_readability AndMaintenanceThing.
File.absolute_path(rel_path) is a good way to take the starting directory and return the absolute path given a relative directory.
File.foreach is a very fast way to read a file, faster than slurping it using something like File.read. It is also scalable whereas File.read is not.

what is a portable way in Ruby to check where STDIN will block if you attempt to read from it?

I would like to find out if there is a portable way to check in a Ruby script whether it will block if it attempts to read from STDIN. The following is an approach that works for Unix (and Cygwin) but not native Win32. (It is based on a Perl approach I learned long ago.)
$ cat read-stdin.rb
#! /usr/bin/ruby
# test of reading from STDIN
require 'fcntl'
# Trace info on input objects
$stdout.sync=TRUE if $DEBUG # make sure standard output and error synchronized
$stderr.print "ARGV=#{ARGV}\n" if $DEBUG
$stderr.print "ARGF=#{ARGF}\n" if $DEBUG
# See if input available, showing usage statement if not
blocking_stdin = FALSE
if (defined? Fcntl::F_GETFL) then
$stderr.print "F_GETFL=#{Fcntl::F_GETFL} O_RDWR=#{Fcntl::O_RDWR}\n" if $DEBUG
flags = STDIN.fcntl(Fcntl::F_GETFL, 0)
$stderr.print "flags=#{flags}\n" if $DEBUG
blocking_stdin = TRUE if ((flags & Fcntl::O_RDWR) == Fcntl::O_RDWR)
$stderr.print "blocking_stdin=#{blocking_stdin}\n" if $DEBUG
end
if (blocking_stdin && (ARGV.length == 0)) then
$stderr.print "usage: #{$0} [-]\n"
Process.exit
end
# Read input and output it
$stderr.print "Input:\n" if $DEBUG
input_text = ARGF.read()
$stderr.print "Output:\n" if $DEBUG
print "#{input_text}\n"
Here is the interaction without debugging:
$ grep -v DEBUG read-stdin.rb >| /tmp/simple-read-stdin.rb
$ echo hey | ruby /tmp/simple-read-stdin.rb
hey
$ ruby /tmp/simple-read-stdin.rb
usage: /tmp/simple-read-stdin.rb [-]
Here is the interaction with debugging:
$ echo hey | ruby -d read-stdin.rb
ARGV=
ARGF=ARGF
F_GETFL=3 O_RDWR=2
flags=65536
blocking_stdin=false
Input:
Output:
hey
$ ruby -d read-stdin.rb
ARGV=
ARGF=ARGF
F_GETFL=3 O_RDWR=2
flags=98306
blocking_stdin=true
usage: read-stdin.rb [-]
I don't know if it is universally portable and I also don't know if it is considered a good idea (blocking isn't such a bad concept) but there is a non-blocking read method in IO. You can use it like this:
chunk = nil
begin
chunk = STDIN.read_nonblock(4096)
rescue Errno::EAGAIN
# Handle the case if it would block
chunk = 'nothing there...'
end
Though, I think it's quite disappointing it doesn't work without specifying a buffer size like IO#read does it, but working around this by using a loop should be quite easy.

Get inexistent system user info without the output

This is my code for getting all system users:
def get_all_system_users
user_paths = Dir["#{ENV['HOME']}/../*"]
users = user_paths.map { |path| path.split("..")[-1].gsub(/\W/, '') }
users.select { |user| %x{id #{user}}.include?("uid") }
end
The problem is the id #{user} command which returns an output for inexistent users that bubbles all the way, exactly like a puts or pp.
How can I mute it but still evaluate the output of the command?
I'd prefer a more direct way: (EDIT: Updated to work with OSX, I think)
def get_all_system_users
`dscacheutil -q user|grep name:|cut -d: -f2`.split
end
Or, to handle multiple OSes:
require 'rbconfig'
def get_all_system_users
case RbConfig::CONFIG['host_os']
when /mac|darwin/i
`dscacheutil -q user|grep name:|cut -d: -f2`.split
when /linux/i
`cat /etc/passwd|grep "/home"|cut -d: -f1`.split
else
raise "InferiorOS Error" #or whatever
end
end
You may try to redirect the stderr to stdout (or dev/null), but it depends on your shell:
%x{id #{user} 2>&1}
..and you will need to detect when the utility returned a failure code:
if $?.success?
It's easier to just parse the /etc/passwd file:
Hash[File.readlines('/etc/passwd').map { |line|
line.split ':'
}.select { |field|
field[5].index('/home/') == 0 && File.directory?(field[5])
}.map { |field|
[field[0], field[2].to_i]
}]
This will return a hash with username as key and uid as value for
all users with an existing home directory under /home/.

How can you check to see if a file exists (on the remote server) in Capistrano?

Like many others I've seen in the Googleverse, I fell victim to the File.exists? trap, which of course checks your local file system, not the server you are deploying to.
I found one result that used a shell hack like:
if [[ -d #{shared_path}/images ]]; then ...
but that doesn't sit well with me, unless it were wrapped nicely in a Ruby method.
Has anybody solved this elegantly?
In capistrano 3, you can do:
on roles(:all) do
if test("[ -f /path/to/my/file ]")
# the file exists
else
# the file does not exist
end
end
This is nice because it returns the result of the remote test back to your local ruby program and you can work in simpler shell commands.
#knocte is correct that capture is problematic because normally everyone targets deployments to more than one host (and capture only gets the output from the first one). In order to check across all hosts, you'll need to use invoke_command instead (which is what capture uses internally). Here is an example where I check to ensure a file exists across all matched servers:
def remote_file_exists?(path)
results = []
invoke_command("if [ -e '#{path}' ]; then echo -n 'true'; fi") do |ch, stream, out|
results << (out == 'true')
end
results.all?
end
Note that invoke_command uses run by default -- check out the options you can pass for more control.
Inspired by #bhups response, with tests:
def remote_file_exists?(full_path)
'true' == capture("if [ -e #{full_path} ]; then echo 'true'; fi").strip
end
namespace :remote do
namespace :file do
desc "test existence of missing file"
task :missing do
if remote_file_exists?('/dev/mull')
raise "It's there!?"
end
end
desc "test existence of present file"
task :exists do
unless remote_file_exists?('/dev/null')
raise "It's missing!?"
end
end
end
end
May be you want to do is:
isFileExist = 'if [ -d #{dir_path} ]; then echo "yes"; else echo "no"; fi'.strip
puts "File exist" if isFileExist == "yes"
I have done that before using the run command in capistrano (which execute a shell command on the remote server)
For example here is one capistrano task which will check if a database.yml exists in the shared/configs directory and link it if it exists.
desc "link shared database.yml"
task :link_shared_database_config do
run "test -f #{shared_path}/configs/database.yml && ln -sf
#{shared_path}/configs/database.yml #{current_path}/config/database.yml ||
echo 'no database.yml in shared/configs'"
end

Resources