Running cmd commands through ruby - ruby

I am writing a program which execute an other program written in c, here is my first try
require 'Open3'
system 'tcc temp.c'
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
stdin.puts '21\n'
STDOUT.puts stdout.gets
end
actual output:
Enter the temperature in degrees fahrenheit: The converted temperature is -6.11
desired output:
Enter the temperature in degrees fahrenheit: 21
The converted temperature is -6.11
and if you know a better way to do that please tell me, i am new to ruby.

You seem to have at least two potential issues:
Your newline will not expand inside single quotes. To include a newline within a string, you need to use double-quotes such as "21\n".
In some cases, you actually need a carriage return rather than a newline. This is especially true when trying to do Expect-like things with a terminal. For example, you may find you need \r instead of \n in your string.
You definitely need to fix the first thing, but you may need to try the second as well. This is definitely one of those "your mileage may vary" situations.

It seems like you're expecting 21 to appear on your screen because it does when you run temp.exe and type in 21. The reason it appears on your screen under those circumstances is that you're typing them into your shell, which "echoes" back everything you type.
When you run the program via Ruby, though, there's no shell and no typing, so 21 doesn't appear on your screen even though it's correctly being sent to the program's standard input.
The simplest solution is pretty simple. Just write it to Ruby's standard output as well:
require 'Open3'
system 'tcc temp.c'
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
STDOUT.puts "21"
stdin.puts '"21"
STDOUT.puts stdout.gets
end
(You'll note that I took out \n—IO#puts adds that for you.)
This is a little repetitive, though. You might define a simple method to take care of it for you:
def echo(io, *args)
puts *args
io.puts *args
end
Then:
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
echo(stdin, "21")
puts stdout.gets
end

Related

Intercepting output to STDERR

I have a Ruby application which- unexpectedly - from time to time outputs a lone line feed character to stderr. I would like to find the place in my program where this happens. Static analysis (grepping case-insensitively for stderr) did not reveal anything useful, i.e. while there were some STDERR.puts statements, they always output also something else besides the \n, so they can't be the culprit.
Does anybody have some idea how I can somehow catch every write to stderr, and if it happens, at least print a backtrace, so that I can see where this write originates?
If you need to trap all calls to a particular object there's a lot of utility in method_missing:
class Exploder
def method_missing(name, *args)
raise "Method #{name} was called!"
end
end
You can then substitute it and trap calls:
$stderr = Exploder.new
Any call like $stderr.write will now raise an exception.

Why Can't You See Return Value of a Ruby Script in Command Line?

My question is a follow up to this question: No return on command line when running Ruby script because the answer doesn't offer an explanation:
Take the following file, script.rb:
def hello(names)
names.map {|name| "#{name} is awesome!"}
end
hello(["mark", "tony", "scott"])
When executing the file on the command line with ruby script.rb the return value of the following function does not appear. However, testing the method in IRB or by dropping into the code with PRY outputs an explicit return value. Why isn't the return value visible via script execution?
P.S. I am aware that using puts will output code into the terminal but I'm interested in why the return value doesn't output.
Because both IRB or Pry are REPL's
REPL stands for: read, evaluate, print and loop. And that's exactly what both Pry and IRB are doing.
They will first read your input, evaluate your code, print the result of the code execution and then start over.
A Ruby script can't return a value directly like you want it to, the Bash shell works in the same way, a Bash function can't return a string directly. You can return a string (with stdout) and assign it to the variable.
~:$~ cat scr.rb
~:$~ puts "Something here"
~:$~ echo $(ruby ./scr.rb)
Something here
~:$~ myvar=$(echo $(ruby ./scr.rb))
~:$~ echo $myvar
Something here
It's really simple: Bash (or whatever shell you are using) and Ruby are different programming languages. Bash doesn't know anything about Ruby. It doesn't know what a " Ruby return" is, it doesn't know what a "Ruby array" is, it doesn't know what a "Ruby string" is. Therefore, you simply cannot possibly return a Ruby object to Bash.
In fact, the shell usually just uses some operating system function to execute the Ruby script (e.g. the classical fork and exec or something like vfork or clone). If you wanted to return values this way, the operating system kernel would have to know about the semantics of every programming language ever invented plus every programming language that is going to be invented in the future. That is just not feasible.
Note that a command can return a value to the shell, namely an integer between 0 and 255 intended as a status code (with 0 meaning "success" and nonzero meaning "error"), and you can set that return value by calling Kernel#exit.
I used to have the same question myself when I started coding. If you have a closer look at your code you can see why it doesn't print anything. You are actually no asking it in your code. Imagine having a huge script of thousands of lines and you want to execute it. You would have millions of pointless outputs if ruby myscript.rb worked the same way as the REPLs.
In addition, if you do want it to work that way, you can just do require the script inside the REPL session ( require_relative 'your_script' ) and then if you call your function hello it will work the way you describe.
I can use the ruval gem. It evaluates each statement and returns its value.
$ ruval names.rb
def hello(names)
names.map {|name| "#{name} is awesome!"}
end
=> hello
hello(["mark", "tony", "scott"])
=> ["mark is awesome!", "tony is awesome!", "scott is awesome!"]

How do I pass an angle bracket ("<") to IO.popen in Ruby?

This command works fine when using backticks to make a system call:
aspell -a < /path/textfile
However, if I try this it does not work:
result = IO.popen(["aspell", "-a", "<", "/path/textfile"]).read
It seems to be an issue with the angle bracket, because this works fine:
result = IO.popen([ "aspell", "--help"]).read
What am I doing wrong here?
That's a shell operation, and as popen is interfacing directly with your command, you can't do it. Instead you assume the responsibility for doing this, which is why the Open3 library exists and things like the popen2 method in particular:
Adapting your code:
require 'open3'
Open3.popen2('aspell', '-a') do |stdin, stdout, wait_thr|
# Feed the contents of /path/textfile into the STDIN of this
# subprocess.
stdin.write(File.read('/path/textfile'))
stdin.close
# Read the results back
result = stdout.read
end
This might be advantageous since you no longer need to write to a temporary file to do operations like this, you can just feed in data directly through the stdin pipe.

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/

How to tell STDIN to stop reading?

So I am studying Build Awesome Command-Line Applications in Ruby. On page 81, we're supposed to use STDIN to enter more than one task into a project.
File.open(global_options[:filename], 'a+') do |todo_file|
if task_names.empty?
puts "Reading new tasks from stdin..."
task_names = STDIN.readlines.map {|a| a.chomp}
end
tasks = 0
task_names.each do |task|
todo_file.puts [task, Time.now].join(', ')
tasks+=1
end
if tasks == 0
raise "You must provide tasks on the command-line or standard input"
end
end
The usual way to enter tasks into a project it's like this $todo new "Rake leaves but with the code above we can to what's in the example below.
It does work. But how do I tell STDIN to stop listening? The example on how to use it is this...
$ todo new
Rake leaves
Take out trash
Clean garage
Put away dishes
^D
What does the ^D represent?
It’s an end-of-file character. You can type this literally on Unix systems with Ctrl+D or on Windows with Ctrl+Z. The traditional way of displaying the Ctrl modifier is with a ^ prefix, e.g., ^D.
Be aware that this closes standard input entirely. If you want to read more data after entering these lines, you’ll need to check the input itself for a different delimiter—for instance, an empty line.
You can close STDIN by pressing Ctrl-d on Unix-like systems or Ctrl-z on Windows.
What does the ^6 represent?
Are you sure it says ^6 and not ^d? If so, that's probably a typo.

Resources