Get inexistent system user info without the output - ruby

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/.

Related

How to change rvm gemset over ssh on os x server

ok
I don't know how to change rvm version over ssh under os x server.
What I do:
Login on server over ssh
run script (below) and catch error
Error: 'rvm is not a funciton, many-many-words'
What I have as script:
use File::Spec;
my $server_directory = File::Spec->catfile($ENV{HOME},'MyProject');
my $exec_file = File::Spec->catfile($server_directory,'run_script.rb');
my $run_ruby_script = qq'bundle exec ruby $exec_file'.' '.join(' ',#ARGV);
# reload bash profile
print qx(source $ENV{HOME}/.bash_profile);
print qx(source $ENV{HOME}/.bashrc);
# reload ruby
print qx(source $ENV{HOME}/.rvm/scripts/rvm);
my $ruby_setup = qq([[ -s "$ENV{HOME}/.rvm/scripts/rvm" ]] && source "$ENV{HOME}/.rvm/scripts/rvm");
print $ruby_setup. "\n";
# change directory
chdir($server_directory);
# configure gemset name
my $version = qx(cat .ruby-version);
chomp($version);
my $gemset = qx(cat .ruby-gemset);
chomp($gemset);
my $change_rvm_gemset = qq(rvm use $version\#$gemset);
print qx($ruby_setup && $change_rvm_gemset);
print qx(rvm current);
Ok, after all.
def exec_via_bash(line)
puts %Q(#{line})
exec = 'bash -c "#{line}"'
puts `#{exec}`
end
def RubySetup
# reload bash profile
homedir = ENV['HOME']
exec_via_bash %Q(source #{homedir}/.bash_profile);
exec_via_bash %Q(source #{homedir}/.bashrc);
# reload ruby
exec_via_bash %Q(source #{homedir}/.rvm/scripts/rvm);
ruby_setup = %Q([[ -s "#{homedir}/.rvm/scripts/rvm" ]] && source "#{homedir}/.rvm/scripts/rvm")
puts ruby_setup
ruby_setup
end
if ARGV.empty?
puts "there is not enough arguments passed. maybe you forget ruby file to exec?"
exit(1)
end
ruby_script_path = ARGV.shift;
exec_file_absolute_path = File.expand_path(ruby_script_path)
unless File.exists? exec_file_absolute_path
puts "file #{exec_file_absolute_path} doesn't exists!"
exit(1)
end
exec_file_directory = File.dirname(exec_file_absolute_path)
exec_bundle = %Q'bundle exec ruby #{exec_file_absolute_path}' + ' ' + ARGV.join(' ')
# change directory
Dir.chdir(exec_file_directory);
# print %x(ls);
# configure gemset name
version = %x(cat .ruby-version).strip;
gemset = %x(cat .ruby-gemset).strip;
change_rvm_gemset = %Q(rvm use #{version}\##{gemset});
ruby_setup = RubySetup()
exec_bash_login_line = [ruby_setup, change_rvm_gemset, exec_bundle].join ' && ';
puts 'exec bash login line: ' + exec_bash_login_line
forced = %Q(bash --login -c '#{exec_bash_login_line}');
puts forced, "\n";
puts %x(#{forced});
ok, this script is not a kind of beauty, but it works well.
Example of usage?
ruby script.rb ~/bla/bla/bla/run_your_program.rb --first_argument --second_argument a,b,c --etc
As I said before:
I've already on the server via ssh.
So, I need to run scripts via launchd.
And I should do it with
# part of launchd worker
<string>bash</string>
<string>-c</string>
<string>ruby ~/PathToCharmScript.rb -r a</string>
P.S:
Please, help me with improvements of this script for others!
Here is a gist: wow_this_works

How to replace " for ' in all csv files in one directory?

I need to replace " for ' in all csv files in one directory. I wonder if there is either
nice one line piece of code to do that or
if I use the fastest way to solve what I do
My code is
files = Dir["*.csv"]
files.each do |file_name|
File.open(file_name, "w") {|file|
file.puts File.read(file_name).gsub('"', "'")
}
end
update
ruby 1.9.3p194 (2012-04-20) [i386-mingw32]
I prefer ruby only solution
You can edit the files in place like so:
ruby -pi~ -e "gsub(/\"/, \"'\")" *.csv
Windows or a *nix system? If you're on something with sed, let it do what it's designed to do:
sed "s/\"/'/" < file_to_fix > updated_file
sed is used for similar tasks constantly and is blindingly fast. I wouldn't embed it in a Ruby script at all, but instead would use it at the command-line.
The problem with the accepted answer is that it is asking you to call an external script (yes, it is ruby but external non the less) instead of using ruby.
How about:
def inplace_edit(file, bak, &block)
old_argv = Array.new(ARGV)
old_stdout = $stdout
ARGV.replace [file]
ARGF.inplace_mode = bak
ARGF.lines do |line|
yield line
end
ARGV.replace old_argv
$stdout = old_stdout
end
my_files_array.each do |file|
inplace_edit file, '.bak' do |line|
STDOUT.puts "[Debug] #{line}"
line = line.gsub(/search1/,"replace1")
line = line.gsub(/search2/,"replace2")
print line
end
end
If you don't want to create a backup of every file then change '.bak' to ''.

How can I pipe data to a process started via Net::SSH on stdin?

I'm generating a data feed on the local machine, that I want to pipe into a remote process via Net::SSH.
Something like
echo foosball | sed 's/foo/bar/g'
Just that the echo foosball part would be the data feed on the local machine.
What I'm NOT looking for is:
data = "foosball"
ssh.exec!("echo #{data} | sed 's/foo/bar/g'")
I really want a stream of data piped into the process in real time ;)
Okay, I figured it out:
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
res = ""
c = Net::SSH.start("127.0.0.1", "xxx", :password => "xxx")
c.open_channel do |channel|
channel.exec("sed 's/foo/bar/g'") do |ch, success|
channel.on_data do |ch,data|
res << data
end
channel.send_data "foosball"
channel.eof!
end
end
c.loop
puts res # => "barsball"

How do I handle a missing mandatory argument in Ruby OptionParser?

In OptionParser I can make an option mandatory, but if I leave out that value it will take the name of any following option as the value, screwing up the rest of the command line parsing.
Here is a test case that echoes the values of the options:
$ ./test_case.rb --input foo --output bar
output bar
input foo
Now leave out the value for the first option:
$ ./test_case.rb --input --output bar
input --output
Is there some way to prevent it taking another option name as a value?
Thanks!
Here is the test case code:
#!/usr/bin/env ruby
require 'optparse'
files = Hash.new
option_parser = OptionParser.new do |opts|
opts.on('-i', '--input FILENAME', 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o', '--output FILENAME', 'Output filename - required') do |filename|
files[:output] = filename
end
end
begin
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
$stderr.print "Error: " + $! + "\n"
exit
end
files.keys.each do |key|
print "#{key} #{files[key]}\n"
end
What you want to do is not a good idea. What if you really have a file named "--output"? This is a perfectly valid filename on Unix. Every Unix program's option parsing works the way the ruby one is doing, so you shouldn't change it, because then your program will be arbitrarily different from everything else, which is confusing and violates the "principle of least surprise."
The real question is: why are you having this problem in the first place? Perhaps you're running your program from another program, and the parent program is providing a blank filename as the parameter to --input, which makes it see --output as the parameter to --input. You can work around this by always quoting the filenames you pass on the command line:
./test_case.rb --input "" --output "bar"
Then --input will be blank, and that's easy to detect.
Also note that if --input is set to --output (and --output is not a real file) you can just try to open the --input file. If it fails, print a message like:
can't open input file: --output: file not found
And that should make it clear to the user what they did wrong.
try this:
opts.on('-i', '--input FILENAME', 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o', '--output FILENAME', 'Output filename - required') do |filename|
files[:output] = filename
end
opts.on("-h", "--help", "Show this message") do
puts opts
exit
end
begin
ARGV << "-h" if ARGV.size != 2
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
$stderr.print "Error: " + $! + "\n"
exit
end
In this case, the mandatory --output option is missing, so do this after calling parse!:
unless files[:input] && files[:output]
$stderr.puts "Error: you must specify both --input and --output options."
exit 1
end
OK - this works - the regular expression in the on() call allows any string as long as it doesn't start with a '-'
If I don't pass an argument to --input and there is another option downstream then it will take that option key as the argument to --input. (e.g. --input --output). The regexp catches that and then I check the error message. If the argument it reports starts with '-' I output the correct error message, namely that there is a missing argument. Not pretty but it seems to work.
Here is my working test case:
#!/usr/bin/env ruby
require 'optparse'
files = Hash.new
option_parser = OptionParser.new do |opts|
opts.on('-i FILENAME', '--input FILENAME', /\A[^\-]+/, 'Input filename - required') do |filename|
files[:input] = filename
end
opts.on('-o FILENAME', '--output FILENAME', /\A[^\-]+/, 'Output filename - required') do |filename|
files[:output] = filename
end
end
begin
option_parser.parse!(ARGV)
rescue OptionParser::ParseError
if $!.to_s =~ /invalid\s+argument\:\s+(\-\-\S+)\s+\-/
$stderr.print "Error: missing argument: #{$1}\n"
else
$stderr.print "Error: " + $! + "\n"
end
exit
end
files.keys.each do |key|
print "#{key} #{files[key]}\n"
end

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