Getting Console Input with Ruby's Docopt - ruby

I'm using Docopt in Ruby to parse my command options, and later in the script I am getting console input using gets.chomp. The problem is that all of the args from the running the program are still left in ARGF after Docopt does its parsing with options = Docopt::docopt(doc), and doing a gets command takes from ARGF before it tries gets'ing from STDIN.
I've tried to clear ARGF, but doing ARGF.gets for some reason tries to run the input as a command. I think clearing ARGF or using another input method could both be solutions, but I haven't found anything yet. I have to imagine that I'm not the first to try to get interactive command line input in Ruby with Docopt, so I'm hoping the answer is out there.
Some more code for those who would like it:
#!/usr/bin/ruby
require 'docopt'
doc=<<eos
Usage:
account_creator.rb --noldap [options]
Options:
-h, --help Show help page
-l, --ldap
-n, --noldap
-s SERVER With or without http[s]://, followed by port
--ad
--od
-d NUM
-u USER
-p PASS
-o OUTPUT-FILE Default behavior is to append output to file if it exists
eos
options = {}
begin
options = Docopt::docopt(doc)
rescue Docopt::Exit => e
puts e.message
exit 1
end
if options['-u']
username = options['-u']
else
while username.eql? '' or username == nil
puts "Enter Username:"
username = Kernel.gets.chomp.strip
end
end

This is unrelated to docopt. Try it on its own:
$ cat test.rb
#!/usr/bin/ruby
puts "Enter Username:"
username = gets
$ ./test.rb something
Enter Username:
./test.rb:4:in `gets': No such file or directory - something (Errno::ENOENT)
from ./test.rb:4:in `gets'
from ./test.rb:4:in `<main>'
Kernel.gets in ruby uses ARGF.gets. Using STDIN.gets should get you your expected behavior. See this SO question.

Related

Require a file to be called from the command line

So I know how to use optparser to use the command line to call a specific method in my program. But, is there a way to use optparse where the user is required to specify a file in order to have the command work? Like for example when using this code:
test.rb
#!/usr/bin/ruby
read = File.readlines(file)
puts read
The user would be required to specify a specific file the program needs to read.
test.rb -b test.txt
Is there a way to do this or am I still too new to ruby to fully understand how it works?
I don't know about optparse, but you can do something like this perhaps:
#test.rb
#!/usr/bin/ruby
file = ARGV[0]
read = File.readlines(file)
puts read
then run the file in command line, passing the file name as an argument. Where ARGV[0] corresponds with the first argument passed:
$ ruby test2.rb test2.rb
##test.rb
##!/usr/bin/ruby
#file = ARGV[0]
#read = File.readlines(file)
#puts read

Ruby gets method throws an exception when arguments are passed from the console

I have experienced some ODD behavior from the code below:
require 'CSV'
$DEBUG = ARGV.empty? ? false : ARGV[0] #Global debug flag.
class PhoneBook
#class code here etc etc
end
PhoneBook.start_dir = "file-io-samples/phonebooks/"
puts "Enter a phonebook!"
name = gets #This is the problem.
puts "Using #{name}.."
When I pass true to have $DEBUG set to true on execution I get an error from name = gets and I have no idea why. If I don't pass parameters via the command line everything works fine.
This is the error output:
C:\Pickaxe>ruby PhoneBook.rb
Enter a phonebook!
Hurrah! Works
Using Hurrah! Works
..
C:\Pickaxe>ruby PhoneBook.rb true
Enter a phonebook!
Exception `Errno::ENOENT' at PhoneBook.rb:62 - No such file or directory - true
PhoneBook.rb:62:in `gets': No such file or directory - true (Errno::ENOENT)
from PhoneBook.rb:62:in `gets'
from PhoneBook.rb:62:in `<main>'
C:\Pickaxe>
If I need to I can post the class definition, but I don't think it's part of the problem.
gets reads from stdin if no arguments are passed, and from the file that was passed as an argument otherwise. You are passing an argument true, ergo gets tries to read from a file named true, which apparently doesn't exist.
This is the very first sentence of the documentation of gets:
Returns (and assigns to $_) the next line from the list of files in ARGV (or $*)
This wouldn't cause a problem on *nix, but I expect Windows, or Ruby on Windows, isn't handling the additional command-line parameter the same way. On *nix, we can use -- between the script name and the parameter to tell the OS not to pass the parameter as a flag. In other words, Ruby wouldn't see true, your script would.
ruby some_script.rb -- options
But, in general, I think you're doing it wrong and recommend handling your command-line options in a standard way by using the OptionParser class:
require 'optparse'
OptionParser.new do |opt|
opt.on('-d', '--[no-]debug') { |o| $DEBUG = o }
end.parse!
puts $DEBUG
Running that several times on my Mac OS system, with different parameters, gives me:
$ ruby test.rb
false
$ ruby test.rb --no-debug
false
$ ruby test.rb -d
true
$ ruby test.rb --debug
true
You might still have to use -- to tell the OS and called app which parameters belong to what.

Ruby, run linux commands one by one, by SSH and LOG everything

I want to write code in Ruby witch net::ssh that run commands one by one on remote linux machine and log everything (called command, stdout and stderr on linux machine).
So I write function:
def rs(ssh,cmds)
cmds.each do |cmd|
log.debug "[SSH>] #{cmd}"
ssh.exec!(cmd) do |ch, stream, data|
log.debug "[SSH:#{stream}>] #{data}"
end
end
end
For example if I want to create on remote linux new folders and file: "./verylongdirname/anotherlongdirname/a.txt", and list files in that direcotry, and find firefox there (which is stupid a little :P) so i call above procedure like that:
Net::SSH.start(host, user, :password => pass) do |ssh|
cmds=["mkdir verylongdirname", \ #1
"cd verylongdirname; mkdir anotherlongdirname, \ #2
"cd verylongdirname/anotherlongdirname; touch a.txt", \ #3
"cd verylongdirname/anotherlongdirname; ls -la", \ #4
"cd verylongdirname/anotherlongdirname; find ./ firefox" #5 that command send error to stderr.
]
rs(ssh,cmds) # HERE we call our function
ssh.loop
end
After run code above i will have full LOG witch informations about executions commands in line #1,#2,#3,#4,#5. The problem is that state on linux, between execude commands from cmds array, is not saved (so I must repeat "cd" statement before run proper command). And I'm not satisfy with that.
My purpose is to have cmds tables like that:
cmds=["mkdir verylongdirname", \ #1
"cd verylongdirname", \
"mkdir anotherlongdirname", \ #2
"cd anotherlongdirname", \
"touch a.txt", \ #3
"ls -la", \ #4
"find ./ firefox"] #5
As you see, te state between run each command is save on the linux machine (and we don't need repeat apropriate "cd" statement before run proper command). How to change "rs(ssh,cmds)" procedure to do it and LOG EVERYTHING (comand,stdout,stdin) like before?
Perhaps try it with an ssh channel instead to open a remote shell. That should preserve state between your commands as the connection will be kept open:
http://net-ssh.github.com/ssh/v1/chapter-5.html
Here's also an article of doing something similar with a little bit different approach:
http://drnicwilliams.com/2006/09/22/remote-shell-with-ruby/
Edit 1:
Ok. I see what you are saying. SyncShell was removed from Net::SSH 2.0. However I found this, which looks like it does pretty much what SyncShell did:
http://net-ssh-telnet.rubyforge.org/
Example:
s = Net::SSH.start(host, user)
t = Net::SSH::Telnet.new("Session" => s, "Prompt" => %r{^myprompt :})
puts t.cmd("cd /tmp")
puts t.cmd("ls") # <- Lists contents of /tmp
I.e. Net::SSH::Telnet is synchronous, and preserves state, because it runs in a pty with your remote shell environment. Remember to set the correct prompt detection, otherwise Net::SSH::Telnet will appear to hang once you call it (it's trying to find the prompt).
You can use pipe instead:
require "open3"
SERVER = "..."
BASH_PATH = "/bin/bash"
BASH_REMOTE = lambda do |command|
Open3.popen3("ssh #{SERVER} #{BASH_PATH}") do |stdin, stdout, stderr|
stdin.puts command
stdin.close_write
puts "STDOUT:", stdout.read
puts "STDERR:", stderr.read
end
end
BASH_REMOTE["ls /"]
BASH_REMOTE["ls /no_such_file"]
Ok, finally with the help of #Casper i get the procedure (maby someone use it):
# Remote command execution
# t=net::ssh:telnet, c="command_string"
def cmd(t,c)
first=true
d=''
# We send command via SSH and read output piece by piece (in 'cm' variable)
t.cmd(c) do |cm|
# below we cleaning up output piece (becouse it have strange chars)
d << cm.gsub(/\e\].*?\a/,"").gsub(/\e\[.*?m/,"").gsub(/\r/,"")
# when we read entire line(composed of many pieces) we write it to log
if d =~ /(^.*?)\n(.*)$/m
if first ;
# instead of the first line (which has repeated commands) we log commands 'c'
#log.info "[SSH]>"+c;
first=false
else
#log.info "[SSH] "+$1;
end
d=$2
end
end
# We print lines that were at the end (in last piece)
d.each_line do |l|
#log.info "[SSH] "+l.chomp
end
end
And we call it in code:
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
require 'net/ssh/telnet'
require 'log4r'
...
...
...
Net::SSH.start(host, user, :password => pass) do |ssh|
t = Net::SSH::Telnet.new("Session" => ssh)
cmd(t,"cd /")
cmd(t,"ls -la")
cmd(t,"find ./ firefox")
end
Thanks, bye.
Here's wrapper around Net/ssh here's article http://ruby-lang.info/blog/virtual-file-system-b3g
source https://github.com/alexeypetrushin/vfs
to log all commands just overwrite the Box.bash method and add logging there

Can I get continuous output from system calls in Ruby?

When you use a system call in a Ruby script, you can get the output of that command like this:
output = `ls`
puts output
That's what this question was about.
But is there a way to show the continuous output of a system call? For example, if you run this secure copy command, to get a file from a server over SSH:
scp user#someserver:remoteFile /some/local/folder/
... it shows continuous output with the progress of the download. But this:
output = `scp user#someserver:remoteFile /some/local/folder/`
puts output
... doesn't capture that output.
How can I show the ongoing progress of the download from inside my Ruby script?
Try:
IO.popen("scp -v user#server:remoteFile /local/folder/").each do |fd|
puts(fd.readline)
end
I think you would have better luck using the ruby standard library to handle SCP (as opposed to forking a shell process). The Net::SCP library (as well as the entire Net::* libraries) are full featured and used with Capistrano to handle remote commands.
Checkout http://net-ssh.rubyforge.org/ for a rundown of what is available.
Tokland answered the question as I asked it, but Adam's approach was what I ended up using. Here was my completed script, which does show a running count of bytes downloaded, and also a percentage complete.
require 'rubygems'
require 'net/scp'
puts "Fetching file"
# Establish the SSH session
ssh = Net::SSH.start("IP Address", "username on server", :password => "user's password on server", :port => 12345)
# Use that session to generate an SCP object
scp = ssh.scp
# Download the file and run the code block each time a new chuck of data is received
scp.download!("path/to/file/on/server/fileName", "/Users/me/Desktop/") do |ch, name, received, total|
# Calculate percentage complete and format as a two-digit percentage
percentage = format('%.2f', received.to_f / total.to_f * 100) + '%'
# Print on top of (replace) the same line in the terminal
# - Pad with spaces to make sure nothing remains from the previous output
# - Add a carriage return without a line feed so the line doesn't move down
print "Saving to #{name}: Received #{received} of #{total} bytes" + " (#{percentage}) \r"
# Print the output immediately - don't wait until the buffer fills up
STDOUT.flush
end
puts "Fetch complete!"
have you tried with IO.popen ?
you should be able to read the output while the process is still running and parse it accordingly.
Redirecting stderr to stdout may work for you:
output = `scp user#someserver:remoteFile /some/local/folder/ 2>&1`
puts output
That should capture both stderr and stdout. You can capture stderr only by throwing away stdout:
output = `scp user#someserver:remoteFile /some/local/folder/ 2>&1 >/dev/null`
puts output
You can then use IO.popen.

How to read an open file in Ruby

I want to be able to read a currently open file. The test.rb is sending its output to test.log which I want to be able to read and ultimately send via email.
I am running this using cron:
*/5 * * * /tmp/test.rb > /tmp/log/test.log 2>&1
I have something like this in test.rb:
#!/usr/bin/ruby
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
When I run this code, it only gives me this output:
Start
End
I would expect the output to be something like this:
Start
Start (from the reading of the test.log since it should have the word start already)
End
Ok, you're trying to do several things at once, and I suspect you didn't systematically test before moving from one step to the next.
First we're going to clean up your code:
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
can be replaced with:
puts "Start"
puts File.read("./test.log")
puts "End"
It's plain and simple; There's no need for a method or anything complicated... yet.
Note that for ease of testing I'm working with a file in the current directory. To put some content in it I'll simply do:
echo "foo" > ./test.log
Running the test code gives me...
Greg:Desktop greg$ ruby test.rb
Start
foo
End
so I know the code is reading and printing correctly.
Now we can test what would go into the crontab, before we deal with its madness:
Greg:Desktop greg$ ruby test.rb > ./test.log
Greg:Desktop greg$
Hmm. No output. Something is broken with that. We knew there was content in the file previously, so what happened?
Greg:Desktop greg$ cat ./test.log
Start
End
Cat'ing the file shows it has the "Start" and "End" output of the code, but the part that should have been read and output is now missing.
What happening is that the shell truncated "test.log" just before it passed control to Ruby, which then opened and executed the code, which opened the now empty file to print it. In other words, you're asking the shell to truncate (empty) it just before you read it.
The fix is to read from a different file than you're going to write to, if you're trying to do something with the contents of it. If you're not trying to do something with its contents then there's no point in reading it with Ruby just to write it to a different file: We have cp and/or mv to do those things for us witout Ruby being involved. So, this makes more sense if we're going to do something with the contents:
ruby test.rb > ./test.log.out
I'll reset the file contents using echo "foo" > ./test.log, and cat'ing it showed 'foo', so I'm ready to try the redirection test again:
Greg:Desktop greg$ ruby test.rb > ./test.log.out
Greg:Desktop greg$ cat test.log.out
Start
foo
End
That time it worked. Trying it again has the same result, so I won't show the results here.
If you're going to email the file you could add that code at this point. Replacing the puts in the puts File.read('./test.log') line with an assignment to a variable will store the file's content:
contents = File.read('./test.log')
Then you can use contents as the body of a email. (And, rather than use Ruby for all of this I'd probably do it using mail or mailx or pipe it directly to sendmail, using the command-line and shell, but that's your call.)
At this point things are in a good position to add the command to crontab, using the same command as used on the command-line. Because it's running in cron, and errors can happen that we'd want to know about, we'd add the 2>&1 redirect to capture STDERR also, just as you did before. Just remember that you can NOT write to the same file you're going to read from or you'll have an empty file to read.
That's enough to get your app working.
class FileLineRead
File.open("file_line_read.txt") do |file|
file.each do |line|
phone_number = line.gsub(/\n/,'')
user = User.find_by_phone_number(line)
user.destroy unless user.nil?
end
end
end
open file
read line
DB Select
DB Update
In the cron job you have already opened and cleared test.log (via redirection) before you have read it in the Ruby script.
Why not do both the read and write in Ruby?
It may be a permissions issue or the file may not exist.
f = File.open("test","r")
puts f.read()
f.close()
The above will read the file test. If the file exists in the current directory
The problem is, as I can see, already solved by Slomojo. I'll only add:
to read and print a text file in Ruby, just:
puts File.read("/tmp/log/test.log")

Resources