Mix input/output with Ruby IO? - ruby

I am hoping to write a small method that can interact with a subprocess (bash in this case) and should be able to both write commands and have those commands print their outback back to my shell when running the Ruby file.
So far, I can do something similar with this code:
require 'io/console'
#shell = IO.popen('/bin/bash', 'w')
def run(command)
puts command
#shell.puts command
puts 'Done'
end
run 'var=3'
run 'echo $var'
run 'sleep 2'
run 'ls docs'
#shell.close
And then when I run this code all of the Ruby code is printed first, and only later does any of the shell code get printed:
var=3
Done
echo $var
Done
sleep 2
Done
ls docs
Done
3
<ls output>
I was trying to read some of the tests for io/console as I'm almost certain there exists a really straightforward way to interact with a subprocess like this and get the output inline with the commands being run:
https://github.com/ruby/io-console/blob/master/test/io/console/test_io_console.rb

Related

How can I start a subscript within a perpetually running bash script after a specific string has been printed in the terminal output?

Specifics:
I'm trying to build a bash script which needs to do a couple of things.
Firstly, it needs to run a third party script that I cannot manipulate. This script will build a project and then start a node server which outputs data to the terminal continually. This process needs to continue indefinitely so I can't have any exit codes.
Secondly, I need to wait for a specific line of output from the first script, namely 'Started your app.'.
Once that line has been output to the terminal, I need to launch a separate set of commands, either from another subscript or from an if or while block, which will change a few lines of code in the project that was built by the first script to resolve some dependencies for a later step.
So, how can I capture the output of the first subscript and use that to run another set of commands when a particular line is output to the terminal, all while allowing the first script to run in the terminal, and without using timers and without creating a huge file from the output of subscript1 as it will run indefinitely?
Pseudo-code:
#!/usr/bin/env bash
# This script needs to stay running & will output to the terminal (at some point)
# a string that we need to wait/watch for to launch subscript2
sh subscript1
# This can't run until subscript1 has output a particular string to the terminal
# This could be another script, or an if or while block
sh subscript2
I have been beating my head against my desk for hours trying to get this to work. Any help would be appreciated!
I think this is a bad idea — much better to have subscript1 changed to be automation-friendly — but in theory you can write:
sh subscript1 \
| {
while IFS= read -r line ; do
printf '%s\n' "$line"
if [[ "$line" = 'Started your app.' ]] ; then
sh subscript2 &
break
fi
done
cat
}

Find out if a ruby script is currently running

I have a ruby script that gets triggered every minute by a CRON job called 'process_files.rb'.
process_files.rb executes another ruby script called 'process_client.rb' with paramaters like so:
ruby process_client.rb -c ABC -s 123 -f /path/to/client/file/client_file
Since process_files.rb runs every minute I want to avoid running process_client.rb if a version of it is currently running with same -c parameter. So if process_client.rb is currently running for -c ABC, it will not
execute the ruby command above.
Both of the scripts are in the same directory called /cdir.
This is what I've got in process_files.rb but it is not working:
client = "ABC"
name = "process_client.rb -c #{client}"
needle = `find /cdir -maxdepth 1 -type f -name #{name}`.to_i
if needle > 0
puts "DONT RUN the ruby script for client abc"
else
puts "RUN the ruby script for client abc"
ruby process_client.rb -c ABC -s 123 -f /path/to/client/file/client_file
end
Then before I execute process.files.rb I execute process_client.rb for client ABC, which has some code to put it into sleep mode for 30 seconds, like so:
...some code
sleep 30
...some code
The problem is that process_files.rb never finds the current execution of process_client.rb for client ABC and executes another version when it should not.
Something is probably wrong with the find command but I don't know what it is.
I would use a Ruby script to run everything.
You can have your Ruby script launch your process_files.rb and process_client.rb using Process.spawn you can get the process PID to know if they are running or not.
Obivously you will need to modify the code below to suit your needs, but this should get you started.
def process_client(client)
client_pid = Process.spawn('/usr/bin/ruby', 'process_client.rb', '-c', client)
Process.detach(client_pid)
client_pid
end
def process_files(some_arg)
# do some stuff
end
process_client_pid = process_client('ABC')
loop do
begin
Process.getpgid(process_client_pid)
# This means the process_client.rb script is running
rescue Errno::ESRCH
# Code here for when the client is not being processed
process_client_pid = process_client('ABC')
process_files(some_arg)
sleep 30
end
I think your problem is that you are using the wrong strategy to detect a match from find. It will return a list of filespecs (possibly empty) separated by new lines. Calling to_i on that list doesn't make sense. You could instead pipe it to word count to get the number of lines, and convert that to an int:
`find ... | wc -l`.to_i
Also, in the line that assigns a value to name, did you mean to use backticks rather than double quotes?

Ruby thor command line tool commands

I am planning on making my first command line tool and was wondering about how the naming and commands work.
I would like my tool to function similarly to Git, in the sense that you just install it, then to run commands you just enter git clone or git commit. In many of the examples I have seen the tools are something like thor foo:bar or ./foo.rb bar.
My main question is how can I make it so if my tools name is Foo, and the command in my tool is bar, all the user has to do is run Foo bar in the command line.
In the case of git clone the way it works is that git program is executed and clone is passed to it as a parameter. So, if you have several scripts that should be started in a similar manner, you can create a small launcher. This code is in bash(but the script is sh compatible, so you can safely change the shebang to /bin/sh). It is very easy to write the same thing in any other language.
#!/bin/bash
command=$1
shift
case $1 in
cmd1)
echo "Executing command #1"
./smallcmd1 "$#"
;;
cmd2)
echo "Executing command $command"
./smallcmd2 "$#"
;;
'')
echo 'No option specified'
exit 1
;;
*) echo "Invalid option"
exit 1
;;
esac
exit $?
Where smallcmds are your secondary command scripts or programs.
Now you can execute it:
./mycommand smallcmd1
Additional parameters can be passed as well.
If you place this script into any $PATH directory then you can omit ./ like so:
mycommand smallcmd1
Making an executable with Thor is really simple. All you have to do is:
include the ruby shebang line.
require "thor" in your script.
define your Thor class.
add #{YourThorClassname}.start to the bottom of your script.
Example: my-cli.rb
#!/usr/bin/env ruby
require "thor"
class MyCLI < Thor
desc "foo", "Prints foo"
def foo
puts "foo"
end
end
MyCLI.start
Make the script executable:
chmod a+x my-cli.rb
Now you can type:
./my-cli.rb foo
You can find a similar example and more help in the Thor Wiki.
You can even rename the file to my-cli (without the .rb extension) and it still works because of the ruby shebang inside the file.

Execute bash commands from a Rakefile

I would like to execute a number of bash commands from a Rakefile.
I have tried the following in my Rakefile
task :hello do
%{echo "World!"}
end
but upon executing rake hello there is no output?
How do I execute bash commands from a Rakefile?
NOTE:This is not a duplicate as it's specifically asking how to execute bash commands from a Rakefile.
I think the way rake wants this to happen is with: http://rubydoc.info/gems/rake/FileUtils#sh-instance_method
Example:
task :test do
sh "ls"
end
The built-in rake function sh takes care of the return value of the command (the task fails if the command has a return value other than 0) and in addition it also outputs the commands output.
There are several ways to execute shell commands in ruby. A simple one (and probably the most common) is to use backticks:
task :hello do
`echo "World!"`
end
Backticks have a nice effect where the standard output of the shell command becomes the return value. So, for example, you can get the output of ls by doing
shell_dir_listing = `ls`
But there are many other ways to call shell commands and they all have benefits/drawbacks and work differently. This article explains the choices in detail, but here's a quick summary possibilities:
stdout = %x{cmd} - Alternate syntax for backticks, behind the scenes
it's doing the same thing
exec(cmd) - Completely replace the running process with a new cmd process
success = system(cmd) - Run a subprocess and return true/false
on success/failure (based on cmd exit status)
IO#popen(cmd) { |io| } - Run a subprocess and connect stdout and
stderr to io
stdin, stdout, stderr = Open3.popen3(cmd) - Run a subprocess and
connect to all pipes (in, out, err)
Given that the consensus seems to prefer rake's #sh method, but OP explicitly requests bash, this answer may have some use.
This is relevant since Rake#sh uses the Kernel#system call to run shell commands. Ruby hardcodes that to /bin/sh, ignoring the user's configured shell or $SHELL in the environment.
Here's a workaround which invokes bash from /bin/sh, allowing you to still use the sh method:
task :hello_world do
sh <<-EOS.strip_heredoc, {verbose: false}
/bin/bash -xeu <<'BASH'
echo "Hello, world!"
BASH
EOS
end
class String
def strip_heredoc
gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
end
end
#strip_heredoc is borrowed from rails:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/string/strip.rb
You could probably get it by requiring active_support, or maybe it's autoloaded when you're in a rails project, but I was using this outside rails and so had to def it myself.
There are two heredocs, an outer one with the markers EOS and an inner one with the markers BASH.
The way this works is by feeding the inside heredoc between the BASH markers to bash's stdin. Note that it is running within the context of /bin/sh, so it's a posix heredoc, not a ruby one. Normally that requires the end marker to be in column 1, which isn't the case here because of the indenting.
However, because it's wrapped within a ruby heredoc, the strip_heredoc method applied there de-indents it, placing the entirety of the left side of the inner heredoc in column 1 prior to /bin/sh seeing it.
/bin/sh also would normally expand variables within the heredoc, which could interfere with the script. The single quotes around the start marker, 'BASH', tell /bin/sh not to expand anything inside the heredoc before it is passed to bash.
However /bin/sh does still apply escapes to the string before passing it to bash. That means backslash escapes have to be doubled to make it through /bin/sh to bash, i.e. \ becomes \\.
The bash options -xeu are optional.
The -eu arguments tell bash to run in strict mode, which stops execution upon any failure or reference to an undefined variable. This will return an error to rake, which will stop the rake task. Usually, this is what you want. The arguments can be dropped if you want normal bash behavior.
The -x option to bash and {verbose: false} argument to #sh work in concert so that rake only prints the bash commands which are actually executed. This is useful if your bash script isn't meant to run in its entirety, for example, if it has a test which allows it to exit gracefully early in the script.
Be careful to not set an exit code other than 0 if you don't want the rake task to fail. Usually, that means you don't want to use any || exit constructs without setting the exit code explicitly, i.e. || exit 0.
%{echo "World!"} defines a String. I expect you wanted %x{echo "World!"}.
%x{echo "World!"} executes the command and returns the output (stdout). You will not see the result. But you may do:
puts %x{echo "World!"}
There are more ways to call a system command:
Backticks: `
system( cmd )
popen
Open3#popen3
There are two ways:
sh " expr "
or
%x( expr )
Mind that ( expr ) can be { expr } , | expr | or ` expr `
The difference is, sh "expr" is a ruby method to execute something, and %x( expr ) is the ruby built-in method. The result and action are different. Here is an example
task :default do
value = sh "echo hello"
puts value
value = %x(echo world)
puts value
end
get:
hello # from sh "echo hello"
true # from puts value
world # from puts value
You can see that %x( expr ) will only do the shell expr but the stdout will not show in the screen. So, you'd better use%x( expr ) when you need the command result.
But if you just want to do a shell command, I recommend you use sh "expr". Because sh "irb" will make you go into the irb shell, while %x(irb) will dead.

How do I pass variables from ruby to sh command

I have a rake task that runs, quite a lot of code. At the end, I need to use sftp and ssh to do some stuff. At the moment I'm unable to automate it. Is there a way to pass to stdout?
This seems like a simple question but I can't find the answer anywhere
#some ruby code
#more ruby code
sh "sftp myuser#hots" #this opens the sftp console
sh "put file" #this doesn't get run until sftp is exited
sh "put another_file" #neither does this
#more ruby code
sh "ssh host" # opens the ssh console
sh "some_action_on_host" # this doesn't get run until ssh is exited
I know there will be ways of doing sftp and ssh using ruby but ideally I just want to be able to pipe variables and commands into the console
So you want to run sftp and send a series of commands to it? How about something like:
sftp = IO.popen("sftp myuser#hots", "w+")
sftp << "put file\n"
sftp << "put another file\n"
sftp.flush # make sure to include this
If you don't want to use ruby, then you may want to enclose your shell commands into ` (backtick characters). This string will be passed to Kernel.` method. This method execute the text as an OS shell command and returns the command's output as a string, e.g.:
`ls`
Alternative syntax to ` is %x[]. This way you can write any bash script:
%x[sftp myuser#hots <<COMMAND
put #{file}
quit
COMMAND]
Please note that this syntax support ruby expressions interpolation using #{...} syntax (similar to double-quoted string literals).

Resources