mpg123 and Ruby; "Can't set terminal attributes" - ruby

I am playing a sound file stored locally on my computer in small program. The program runs fine, and the audio plays, however, I keep getting a message on my terminal saying "Can't set terminal attributes".
I am using mpg123 and Ruby.
Hope you guys like DBZ :)
play_audio = fork{ exec 'mpg123','-q', '9000.mp3' }
h = { "Goku" => 9001, "Yamcha" => 4029, "Krillin" => 4034, "Piccollo" => 6701 }
puts "Vegeta, what does scouter say about his power level?\n\n"
h.each do |key, value|
if value > 9000
puts "#{key.upcase}'S POWER LEVEL IS OVER 9000!!!"
play_audio
else
puts "#{key}'s power level is #{value}"
end
end
A picture of the error message

fork { exec 'mpg123','-q', '9000.mp3' }
What is the reason of forking a process here?
The forked process has no terminal and mpg123 fails to set terminal attributes to output what it wants to output.
Possibly shutting it up might help:
fork { exec 'mpg123','-q', '9000.mp3', '>/dev/null', '2>&1' }
But still, just get rid of redundant fork and you are all set.

Related

Ruby script with parrallel threads and logging bash output

I have a ruby script I'm making to upload a directory of roles to chef server. Doing this 1 at a time with a .each loop is slow. So I added parallelism by running each command in separate threads. Now I'm trying to figure out how to store the output of the commands so I can read them back in order of the threads that were created. The roles array is already in alphabetical order. We also use bash_profile aliases for running the knife command with different configuration files for dev and prod.
I've tried many different ways to run the bash command and trying to store the output in an array or to a file etc... Currently this displays the output from each thread as it runs or finishes so out put is hard to read or tell if everything finished correctly and the files that the bash command output is supposed to be redirecting to get created but have empty content.
Sorry if this script isn't the easiest to read. I've only been doing ruby for a little over a year now and self taught myself it when we started to get into chef. I didn't have a programming background before that.
#!/opt/chefdk/embedded/bin/ruby
def print_usage_and_exit
puts 'Need to specify 1 or more role.json files or no arguments to upload all roles'
puts "ruby #{__FILE__} or ruby #{__FILE__} [role1.json] [role2.json] [...]"
exit(1)
end
def fetch_roles
roles = []
current_dir = File.dirname(__FILE__)
Dir.foreach("#{current_dir}/roles") do |role|
next if role == '.' || role == '..' || role == 'README.md'
roles.push(role)
end
roles
end
upload = []
i = 0
roles = (ARGV.empty? ? fetch_roles : ARGV[0..-1])
# Probably redundant, but a cheap check to make sure we're only looking at json files
roles.keep_if { |b| b.end_with?('.json') }
print_usage_and_exit if roles.empty?
print "\nSpecify new knife command if you have seperate knife command for dev and prod created with .bash_profile function."
print "\nLeave blank to use default 'knife' command"
print "\nWhich knife command to use: "
knife = ($stdin.gets.chomp('') ? 'knife' : $stdin.gets.chomp)
print "\n**** Starting upload of roles to chef server ****\n"
roles.each do |role|
upload[i] = Thread.new{
system("bash", "-cl", "#{knife} role from file #{role} > /tmp/#{role}.log")
}
i += 1
end
upload.each {|t| t.join}
roles.each do |role|
logfile = "/tmp/#{role}.log"
logmsg = open(logfile)
print "\n#{logmsg.read}\n"
#FileUtils.rm("/tmp/#{role}.log")
end
print "\n**** Finished uploading roles to chef server ****\n"
The right way to do this is knife upload roles/. That doesn't actually answer your question per se, but I think you'll find it a lot simpler.
I prefer to use Open3's caputure3 function to execute subprocesses, as it makes it easy to handle all the various details ( stdin, stdout, stderr, environment variables, etc ).
Pair that with the use of thread-local data, a built in feature of ruby threads, and you have a pretty easy method of running subprocesses. I'm a big fan of using threads for this kind of concurrency. The GIL prevents ruby from running all the threads concurrently, but the capture3 subprocesses run concurrently anyway, so it doesn't really matter.
require 'open3'
commands = [
'true',
'echo "a more complex command from `pwd`" 1>&2 && echo "and stdout"',
]
threads = []
commands.each_with_index do |cmd, i|
threads[i] = Thread.new do
stdout, stderr, status = Open3.capture3("bash", stdin_data: cmd)
Thread.current['stdout'] = stdout
Thread.current['stderr'] = stderr
Thread.current['status'] = status
end
end
threads.each_with_index do |th,i|
th.join
puts "Thread # #{i}:"
%w( stdout stderr status ).each do |s|
puts "\t#{s}: #{th[s]}"
end
puts
end
The results are exactly what you'd expect:
$ ruby ./t.rb
Thread # 0:
stdout:
stderr:
status: pid 34244 exit 0
Thread # 1:
stdout: and stdout
stderr: a more complex command from /Users/dfarrell/t
status: pid 34243 exit 0
You can use the exit status to give a final summary of how many commands failed or succeeded.

Kill a process called using open3 in ruby

I'm using a command line program, it works as mentioned below:
$ ROUTE_TO_FOLDER/app < "long text"
If "long text" is written using the parameters "app" needs, then it will fill a text file with results. If not, it will fill the text file with dots continuously (I can't handle or modify the code of "app" in order to avoid this).
In a ruby script there's a line like this:
text = "long text that will be used by app"
output = system("ROUTE_TO_FOLDER/app < #{text}")
Now, if text is well written, there won't be problems and I will get an output file as mentioned before. The problem comes when text is not well written. What happens next is that my ruby script hangs and I'm not sure how to kill it.
I've found Open3 and I've used the method like this:
irb> cmd = "ROUTE_TO_FOLDER/app < #{text}"
irb> stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
=> [#<IO:fd 10>, #<IO:fd 11>, #<IO:fd 13>, #<Thread:0x007f3a1a6f8820 run>]
When I do:
irb> wait_thr.value
it also hangs, and :
irb> wait_thr.status
=> "sleep"
How can I avoid these problems? Is it not recognizing that "app" has failed?
wait_thr.pid provides you the pid of the started process. Just do
Process.kill("KILL",wait_thr.pid)
when you need to kill it.
You can combine it with detecting if the process is hung (continuously outputs dots) in one of the two ways.
1) Set a timeout for waiting for the process:
get '/process' do
text = "long text that will be used by app"
cmd = "ROUTE_TO_FOLDER/app < #{text}"
Open3.popen3(cmd) do |i,o,e,w|
begin
Timeout.timeout(10) do # timeout set to 10 sec, change if needed
# process output of the process. it will produce EOF when done.
until o.eof? do
# o.read_nonblock(N) ...
end
end
rescue Timeout::Error
# here you know that the process took longer than 10 seconds
Process.kill("KILL", w.pid)
# do whatever other error processing you need
end
end
end
2) Check the process output. (The code below is simplified - you probably don't want to read the output of your process into a single String buf first and then process, but I guess you get the idea).
get '/process' do
text = "long text that will be used by app"
cmd = "ROUTE_TO_FOLDER/app < #{text}"
Open3.popen3(cmd) do |i,o,e,w|
# process output of the process. it will produce EOF when done.
# If you get 16 dots in a row - the process is in the continuous loop
# (you may want to deal with stderr instead - depending on where these dots are sent to)
buf = ""
error = false
until o.eof? do
buf << o.read_nonblock(16)
if buf.size>=16 && buf[-16..-1] == '.'*16
# ok, the process is hung
Process.kill("KILL", w.pid)
error = true
# you should also get o.eof? the next time you check (or after flushing the pipe buffer),
# so you will get out of the until o.eof? loop
end
end
if error
# do whatever error processing you need
else
# process buf, it contains all the output
end
end
end

Ruby: gets.chomp with default value

Is there some simple way how to ask for a user input in Ruby WHILE providing a default value?
Consider this code in bash:
function ask_q {
local PROMPT="$1"
local DEF_V="$2"
read -e -p "$PROMPT" -i "$DEF_V" REPLY
echo $REPLY
}
TEST=$(ask_q "Are you hungry?" "Yes")
echo "Answer was \"$TEST\"."
Can you achieve similar behaviour with Ruby's gets.chomp?
function ask_q(prompt, default="")
puts prompt
reply = gets.chomp() # ???
return reply
def
reply = ask_q("Are you hungry?", "Yes")
I understand I can sort replicate the functionality in Ruby this way ...
def ask_q(prompt, default="")
default_msg = (default.to_s.empty?) ? "" : "[default: \"#{default}\"]"
puts "${prompt} ${default}"
reply = gets.chomp()
reply = (default.to_s.empty?) ? default : reply
return reply
end
... but it does not seem very pretty. I also need to show the default value manually and the user needs to retype it in the prompt line, if he wants to use modified version of it (say yes! instead of yes).
I'm starting with Ruby now, so there may be a lot of syntax mistakes and I also may be missing something obvious ... Also, I googled a lot but surprisingly found no clue.
TL; DR
To make the question clearer, this is what you should see in terminal and what I am able to achieve in bash (and not in Ruby, so far):
### Terminal output of `reply=ask_q("Are you hungry?" "Yes")`
$ Are you hungry?
$ Yes # default editable value
### Terminal output of `reply=ask_q("What do you want to eat?")`
$ What do you want to eat?
$ # blank line waiting for user input, since there is no second parameter
And the actual situation: I am building bootstrap script for my web apps. I need to provide users with existing configuration data, that they can change if needed.
### Terminal output of `reply=ask_q("Define name of database." "CURR_DB_NAME")`
I don't think it's that fancy functionality, that would require switch to GUI app world.
And as I've said before, this is quite easily achievable in bash. Problem is, that other things are pure pain (associative arrays, no return values from functions, passing parameters, ...). I guess I just need to decide what sucks the least in my case ...
You need to do one of two things:
1) Create a gui program.
2) Use curses.
Personally, I think it's a waste of time to spend any time learning curses. Curses has even been removed from the Ruby Standard Library.
A GUI program:
Here is what a gui app looks like using the Tkinter GUI Framework:
def ask_q(prompt, default="")
require 'tk'
root = TkRoot.new
root.title = "Your Info"
#Display the prompt:
TkLabel.new(root) do
text "#{prompt}: "
pack("side" => "left")
end
#Create a textbox that displays the default value:
results_var = TkVariable.new
results_var.value = default
TkEntry.new(root) do
textvariable results_var
pack("side" => "left")
end
user_input = nil
#Create a button for the user to click to send the input to your program:
TkButton.new(root) do
text "OK"
command(Proc.new do
user_input = results_var.value
root.destroy
end)
pack("side" => "right", "padx"=> "50", "pady"=> "10")
end
Tk.mainloop
user_input
end
puts ask_q("What is your name", "Petr Cibulka")
Calling a function in a bash script from ruby:
.../bash_programs/ask_q.sh:
#!/usr/bin/env bash
function ask_q {
local QUESTION="$1"
local DEFAULT_ANSWER="$2"
local PROMPT="$QUESTION"
read -p "$PROMPT $DEFAULT_ANSWER" USERS_ANSWER #I left out the -i stuff, because it doesn't work for my version of bash
echo $USERS_ANSWER
}
ruby_prog.rb:
answer = %x{
source ../bash_programs/ask_q.sh; #When ask_q.sh is not in a directory in your $PATH, this allows the file to be seen.
ask_q 'Are you Hungry?' 'Yes' #Now you can call functions defined inside ask_q.sh
}
p answer.chomp #=> "Maybe"
Using curses:
require 'rbcurse/core/util/app'
def help_text
<<-eos
Enter as much help text
here as you want
eos
end
user_answer = "error"
App.new do #Ctrl+Q to terminate curses, or F10(some terminals don't process function keys)
#form.help_manager.help_text = help_text() #User can hit F1 to get help text (some terminals do not process function keys)
question = "Are You Hungry?"
default_answer = "Yes"
row_position = 1
column_position = 10
text_field = Field.new(#form).
name("textfield1").
label(question).
text(default_answer).
display_length(20).
bgcolor(:white).
color(:black).
row(row_position).
col(column_position)
text_field.cursor_end
text_field.bind_key(13, 'return') do
user_answer = text_field.text
throw :close
end
end
puts user_answer

Fork child process with timeout and capture output

Say I have a function like below, how do I capture the output of the Process.spawn call? I should also be able to kill the process if it takes longer than a specified timeout.
Note that the function must also be cross-platform (Windows/Linux).
def execute_with_timeout!(command)
begin
pid = Process.spawn(command) # How do I capture output of this process?
status = Timeout::timeout(5) {
Process.wait(pid)
}
rescue Timeout::Error
Process.kill('KILL', pid)
end
end
Thanks.
You can use IO.pipe and tell Process.spawn to use the redirected output without the need of external gem.
Of course, only starting with Ruby 1.9.2 (and I personally recommend 1.9.3)
The following is a simple implementation used by Spinach BDD internally to capture both out and err outputs:
# stdout, stderr pipes
rout, wout = IO.pipe
rerr, werr = IO.pipe
pid = Process.spawn(command, :out => wout, :err => werr)
_, status = Process.wait2(pid)
# close write ends so we could read them
wout.close
werr.close
#stdout = rout.readlines.join("\n")
#stderr = rerr.readlines.join("\n")
# dispose the read ends of the pipes
rout.close
rerr.close
#last_exit_status = status.exitstatus
The original source is in features/support/filesystem.rb
Is highly recommended you read Ruby's own Process.spawn documentation.
Hope this helps.
PS: I left the timeout implementation as homework for you ;-)
I followed Anselm's advice in his post on the Ruby forum here.
The function looks like this -
def execute_with_timeout!(command)
begin
pipe = IO.popen(command, 'r')
rescue Exception => e
raise "Execution of command #{command} unsuccessful"
end
output = ""
begin
status = Timeout::timeout(timeout) {
Process.waitpid2(pipe.pid)
output = pipe.gets(nil)
}
rescue Timeout::Error
Process.kill('KILL', pipe.pid)
end
pipe.close
output
end
This does the job, but I'd rather use a third-party gem that wraps this functionality. Anyone have any better ways of doing this? I have tried Terminator, it does exactly what I want but it does not seem to work on Windows.

How to execute interactive shell program on a remote host from ruby

I am trying to execute an interactive shell program on a remote host from another ruby program. For the sake of simplicity let's suppose that the program I want to execute is something like this:
puts "Give me a number:"
number = gets.chomp()
puts "You gave me #{number}"
The approach that most successful has been so far is using the one I got from here. It is this one:
require 'open3'
Open3.popen3("ssh -tt root#remote 'ruby numbers.rb'") do |stdin, stdout, stderr|
# stdin = input stream
# stdout = output stream
# stderr = stderr stream
threads = []
threads << Thread.new(stderr) do |terr|
while (line = terr.gets)
puts "stderr: #{line}"
end
end
threads << Thread.new(stdout) do |terr|
while (line = terr.gets)
puts "stdout: #{line}"
end
end
sleep(2)
puts "Give me an answer: "
answer = gets.chomp()
stdin.puts answer
threads.each{|t| t.join()} #in order to cleanup when you're done.
end
The problem is that this is not "interactive" enough to me, and the program that I would like to execute (not the simple numbers.rb) has a lot more of input / output. You can think of it as an apt-get install that will ask you for some input to solve some problems.
I have read about net::ssh and pty, but couldn't see if they were going to be the (easy/elegant) solution I am looking for.
The ideal solution will be to make it in such a way that the user does not realize that the IO is being done on a remote host: the stdin goes to the remote host stdin, the stdout from the remote host comes to me and I show it.
If you have any ideas I could try I will be happy to hear them. Thank you!
Try this:
require "readline"
require 'open3'
Open3.popen3("ssh -tt root#remote 'ruby numbers.rb'") do |i, o, e, th|
Thread.new {
while !i.closed? do
input =Readline.readline("", true).strip
i.puts input
end
}
t_err = Thread.new {
while !e.eof? do
putc e.readchar
end
}
t_out = Thread.new {
while !o.eof? do
putc o.readchar
end
}
Process::waitpid(th.pid) rescue nil
# "rescue nil" is there in case process already ended.
t_err.join
t_out.join
end
I got it working, but don't ask me why it works. It was mainly trial/error.
Alternatives:
Using Net::SSH, you need to use :on_process and a Thread: ruby net/ssh channel dies? Don't forget to add session.loop(0.1). More info at the link. The Thread/:on_process idea inspired me to write a gem for my own use: https://github.com/da99/Chee/blob/master/lib/Chee.rb
If the last call in your Ruby program is SSH, then you can exec ssh -tt root#remote 'ruby numbers.rb'. But, if you still want interactivity between User<->Ruby<->SSH, then the previous alternative is the best.

Resources