Prefixing console ouput of system calls from Ruby - ruby

I'd like to create a Ruby script that prefixes the console output. For example:
I want to implement an interface like this:
puts 'MainLogger: Saying hello'
prefix_output_with('MainLogger') do
system 'echo hello'
end
So this shows up in the console:
MainLogger: Saying hello
MainLogger: hello
What would be a good approach to prefix all of the syscall output to have some logger?
Note: I don't care if we echo what the system call is or not

The important point here is that there's no way to know if system will actually produce output. I'm assuming you don't want a blank MainLogger: whenever a system call doesn't print anything, so you'll need to do the prefixes in the shell:
def prefix_system_calls pre
sys = Kernel.instance_method(:system)
# redefine to add prefix
Kernel.send(:define_method, :system) do |cmd|
sys.bind(self)["#{cmd} | sed -e 's/^/#{pre}: /'"]
end
yield
# redefine to call original method
Kernel.send(:define_method, :system) do |*args|
sys.bind(self)[*args]
end
end
system "echo foo"
prefix_system_calls("prefix") do
system "echo bar"
end
system "echo baz"
# foo
# prefix: bar
# baz
This implementation is pretty fragile, though. It doesn't handle all the different ways you can call system, and prefixes containing special shell characters could cause an error.

Related

Define a ruby function to wrap %x

I want to define a function to wrap %x as below
def myx(arg)
puts %x("#{arg}")
end
It's OK to call myx("ls") but failed to call myx("ls /usr"). The error message is
sh: 1: ls /usr: not found
I understand the error. It's because that the whole word ls /usr is taken as a single argument passed to the shell. But how can I fix the problem?
Make sure you quote your entire command inside the string:
def myx(arg)
puts %x("#{arg}")
end
myx('"ls /usr"')
Alternatively backticks don't require the string to be quoted:
def myx(arg)
puts `#{arg}`
end
myx("ls /usr")
Even nicer in my opinion:
def myx(arg)
system(*arg)
end
myx(['ls', '/usr'])
Outputs:
bin
games
include
lib
lib32
lib64
libx32
local
sbin
share
src
The first two pass the entire command as a string to the shell for execution. The last one bypasses the shell and ruby executes it itself.
Do not quote the command line with " in %x.
arg = "ls /usr"
%x("#{arg}")
will send the "ls /usr" as a single argument to the system, as if you execute the following in shell:
$ "ls /usr"
The system will try to locate a command/executable named ls /usr, which leads to the problem you met. So update your method to the following will work
def myx(arg)
puts %x(#{arg})
end
myx('ls /usr')
The previous answer
def myx(arg)
puts %x("#{arg}")
end
myx('"ls /usr"')
works as if you execute the following in the shell:
$ ""ls /usr""
# which results in
$ ls /usr
That's not quoting the command, rather, it uses extra " to clear the quote.

Ruby print last line of files where files are given from stdin re-direction

Want to run Ruby like this
cat *.txt |my_print_last_line.rb
And have the last line of each file printed (might do something interesting if I could first print)
The ARGF class seems promising to solve it.
Tried this:
ARGF.each do |line|
last_line_of_file = line[last]
puts last_line_of_file
end
However ARGF.each seems to be one long string of all files concatenated rather than the individual files.
This has to do with how cat handles multiple files. It just concatenates them:
$ echo 'foo' > foo
$ echo 'bar' > bar
$ cat *
foo
bar
This means that after issuing the cat command there's no way of telling where the individual files started and ended. If you would just invoke your script as ruby my_print_last_line.rb *.txt you would still have the same problem because using ARGF will also simply concatenate all files.
As an alternative, you could take the filenames as parameters to the ruby script to open and read the files directly in ruby:
ARGV.each do |file|
puts IO.readlines(file).last
end
Then invoke your script like this:
$ ruby my_print_last_line.rb *.txt
If you are reading really large files, you may gain a significant performance benefit by using a seek based approach, as described by #DonaldScottWilde in this answer to a similar question.

How do I write shell-like scripts using Ruby?

I have a task of writing a simple Ruby script which would do the following.
Upon execution from the UNIX command line, it would present the user with a prompt at which he should be able to run certain commands, like "dir", "help" or "exit". Upon "exit" the user should return to the Unix shell.
I'm not asking for the solution; I would just like to know how this "shell" functionality can be implemented in Ruby. How do you present the user with a prompt and interpret commands.
I do not need a CLI script that takes arguments. I need something that creates a shell interface.
The type of program you require can easily be made with just a few simple constructs.
I know you're not asking for a solution, but I'll just give you a skeleton to start off and play around with:
#!/usr/bin/env ruby
def prnthelp
puts "Hello sir, what would you like to do?"
puts "1: dir"
puts "2: exit"
end
def loop
prnthelp
case gets.chomp.to_i
when 1 then puts "you chose dir!"
when 2 then puts "you chose exit!"
exit
end
loop
end
loop
Anyways, this is a simplistic example on how you could do it, but probably the book recommended in the comments is better. But this is just to get you off.
Some commands to get you started are:
somevar = gets
This gets user input. Maybe learn about some string methods to manipulate this input can do you some good. http://ruby-doc.org/core-2.0/String.html
chomp will chop off any whitespace, and to_i converts it to an integer.
Some commands to do Unix stuff:
system('ls -la') #=> outputs the output of that command
exit #=> exits the program
Anyways, if you want this kind of stuff, I think it's not a bad idea to look into http://www.codecademy.com/ basically they teach you Ruby by writing small scripts such as these. However, they maybe not be completely adapted to Unix commands, but user input and the likes are certainly handled.
Edit:
As pointed out do use this at the top of your script:
#!/usr/bin/env ruby
Edit:
Example of chomp vs. chop:
full_name = "My Name is Ravikanth\r\n"
full_name.chop! # => "My Name is Ravikanth"
Now if you run chop and there are no newline characters:
puts full_name #=> "My Name is Ravikanth"
full_name.chop! #=> "My Name is Ravikant"
versus:
puts full_name #=> "My Name is Ravikanth\r\n"
full_name.chomp! #=> "My Name is Ravikanth"
full_name.chomp! #=> "My Name is Ravikanth"
See: "Ruby Chop vs Chomp"
Here's a really basic loop:
#!/user/bin/ruby
#
while true do
print "$ "
$stdout.flush
inputs = gets.strip
puts "got your input: #{inputs}"
# Check for termination, like if they type in 'exit' or whatever...
# Run "system" on inputs like 'dir' or whatever...
end
As Stefan mentioned in a comment, this is a huge topic and there are scenarios that will make this complicated. This is, as I say, a very basic example.
Adding to the two other (valid) answers posted so far be wary of using #!/usr/bin/ruby, because ruby isn't always installed there. You can use this instead:
#!/usr/bin/env ruby
Or if you want warnings:
#!/usr/bin/env ruby -w
That way, your script will work irrespective of differences where ruby might be installed on your server and your laptop.
Edit: also, be sure to look into Thor and Rake.
http://whatisthor.com
http://rake.rubyforge.org
Use irb.
I was looking into an alternative to bash and was thinking along the same lines... but ended up choosing fish: http://fishshell.com/
Nonetheless, I was thinking of using irb and going along the lines of irbtools: https://github.com/janlelis/irbtools
Example:
> irb
Welcome to IRB. You are using ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-linux]. Have fun ;)
>> ls #=> ["bin", "share", "opt", "lib", "var", "etc", "src"]
>>
In any case, irb is the ruby shell.
Take a look at cliqr which comes with inbuilt support for build a custom shell https://github.com/anshulverma/cliqr/

Any way to automagically `puts` the last expression in a Ruby script?

I'm working on implementing Project Euler solutions as semantic Ruby one-liners. It would be extremely useful if I could coerce Ruby to automatically puts the value of the last expression. Is there a way to do this? For example:
#!/usr/bin/env ruby -Ilib -rrubygems -reuler
1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
I realize I can simply puts the line, but for other reasons (I plan to eval the file in tests, to compare against the expected output) I would like to avoid an explicit puts. Also, it allots me an extra four characters for the solution. :)
Is there anything I can do?
You might try running it under irb instead of directly under a Ruby interpreter.
It seems like the options -f --noprompt --noverbose might be suitable (.
#!/usr/bin/env irb -f --noprompt --noverbose -Ilib -rrubygems -reuler
'put your one-liner here'
The options have these meanings:
-f: do not use .irbrc (or IRBRC)
--noverbose: do not display the source lines
--noprompt: do not prefix the output (e.g. with =>)
result = calculate_result
puts result if File.exist?(__FILE__)
result of eval is last executed operation just like any other code block in ruby
is doing
puts eval(file_contents)
an option for you?
EDIT
you can make use of eval's second parameter which is variables binding
try the following:
do_not_puts = true
eval(file_contents, binding)
and in the file:
....
result = final_result
if defined?(do_not_puts)
result
else
puts(result)
end
Is it an option to change the way you run scripts?
script.rb:
$_= 1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
invoke with
echo nil.txt | /usr/bin/env/ruby -Ilib -rrubygems -reuler -p script.rb, where nil.txt is a file with a single newline.

Is it possible to stringify parameters in ruby/irb?

I'm new to ruby ... wondering if the following is possible:
I currently run a test app within irb (irb -r test.rb) and manually execute
various command implemented in test.rb. One of these functions is currently implemented as follows:
def cli(cmd)
ret=$client.Cli(cmd)
print ret, "\n"
end
Where $client.Cli() takes a string. I currently type the following in the IRB prompt
> cli "some command with parameters"
This is sent over socket and results are returned
I would like to be able to do this WITHOUT the quotes. This would be just for this command
Is there a way to do this generally in ruby? if not how would you extend irb to do this?
For those who know 'C' this would be like the following:
#define CLI(CMD) cli(#CMD)
CLI(Quadafi and Sheen walk into a bar...)
where the pre-processed output is:
cli("Quadafi and Sheen walk into a bar...")
Thanks
You could actually monkey patch the gets method of the IRB::StdioInputMethod and IRB::ReadlineInputMethod classes, and perform a rewrite if the cli method is called, by adding the following to your test.rb file:
module IRB
def self.add_quotes(str)
str.gsub(/^cli (..+?)(\\+)?$/, 'cli "\1\2\2"') unless str.nil?
end
class StdioInputMethod
alias :old_gets :gets
def gets
IRB::add_quotes(old_gets)
end
end
class ReadlineInputMethod
alias :old_gets :gets
def gets
IRB::add_quotes(old_gets)
end
end
end
This way, any input line matching cli ... will be replaced with cli "..." before it's evaluated.
I don't think it's possible, because the command that you type to irb has to parse as ruby, and all those bare words will report errors like this:
NameError: undefined local variable or method `hello' for main:Object
(My first attempt, I just called it via cli hello.)
But if you didn't mind a more radical change, you could do something like this:
$ cat /tmp/test_cases
hello world
one
two
three
riding the corpse sled
$ ruby -e 'def f(arg) puts arg end' -ne 'f($_)' < /tmp/test_cases
hello world
one
two
three
riding the corpse sled
I just defined a simple function f() here that would show how it works; you could replace f($_) with $Client.cli($_), and set up the $Client global variable in the first -e argument. And you can leave off the < /tmp/test_cases if you want to type them in interactively:
$ ruby -e 'def f(arg) puts arg end' -ne 'f($_)'
hello
hello
world
world
Of course, if you want it to be any more advanced than this, I'd just write a script to do it all, rather than build something hideous from -pe or -ne commands.

Resources