Ruby capture stderr output from bash script execution - ruby-on-rails

I can currently redirect stdout to a string variable in ruby/rails by simply running the command in bash and setting the result to my string variable as follows.
val = %x[ #{cmd} ]
where cmd is a string that represents a bash command.
However, this only captures stdout, for I want to capture stderr and set it to a string in ruby -- any ideas?

Simply redirect it:
val = %x[ #{cmd} 2>&1 ]
If you want to capture output from stderr only, close the file descriptor for stdout after copying it to fd 2.
val = %x[ #{cmd} 2>&1 >/dev/null ]

You can use Open3.popen3:
require 'open3'
stdin, stdout, stderr, wait_thread = Open3.popen3('ping -Z')
# => [#<IO:fd 9>, #<IO:fd 10>, #<IO:fd 12>, #<Thread:0x007fd3d30a0ce0 sleep>]
stderr.gets # => "ping: illegal option -- Z\n"
stdout.gets # => nil

Related

csh - suppress STDERR when using backticks

I'd like to write a bit of code that looks like this:
while ( `ls -1 ${JOB_PREFIX}_${job_counter}_*.out | wc -l` > 0 )
echo "Do something here."
end
But every time there is no ls -1 ${JOB_PREFIX}_${job_counter}*.csh it gives an annoying ls: No match.
Is it possible to suppress this error message but still pipe STDOUT to wc ? I went through the existing questions on this but most of the answers are either not working on csh or tries to combine STDOUT and STDERR with |& which I do not want.
The cleanest way would have been to mute the ls itself, but it is not possible.
You can suppress your entire script, meaning:
If your loops is in a file called myscript.cs and you call your file with some args myscript.cs -arg1 blabla -arg2 bla, add >& to your shell command to redirect stderr to wherever you want. e.g.
myscript.cs -arg1 blabla -arg2 bla >& /dev/null
It's not answering your answer directly, but it should solve your problem.
Edit
Since the comment added that the script should be redirected to another script, you can split your line to two lines:
if `ls -1 ${JOB_PREFIX}_${job_counter}_*.out >& /dev/null` then
while < your while here >
end
endif
csh and tcsh cannot redirect stderr by itself, one of many reasons you should not use it for scripting.
If you're okay with spawning another shell, you can change JOB_COUNTER into an environment variable and do:
while ( `sh -c '2>/dev/null ls -1 ${JOB_PREFIX}_${JOB_COUNTER}_*.out | wc -l'` > 0 )
echo "Do something here."
end
If you want to use a test as a condition in a while loop instead of an expression, you have to get creative.
#!/bin/tcsh -f
set i = 4
while ( 1 )
if ( $i <= 0 ) break
echo hi
# i = ($i - 1)
end
In your case, this might look something like: I changed the wc -l to a grep -q . so that we can use the exit status instead of the result printed to stderr.
#!/bin/tcsh -f
set dir_exit_status = 0
while ( $dir_exit_status = 0 )
( ls -1 ${JOB_PREFIX}_${job_counter}_*.out | grep -q '.') >& /dev/null
set dir_exit_status = $status
end

Redirect Output to stderr

I want to redirect my output to stderr. I have a cron job
function auth_tester {
cd /data/$1/current && bundle exec rake 'authentication:tester' 1> /dev/null
}
which calls a rake task
namespace :authentication do
desc "Automatically runs authentication tester and notifies in case of failure"
task :tester => :environment do
auth_tester_results = AuthenticationTester.perform
exit(auth_tester_results.success? ? 0 : 1)
end
end
If the 'auth_tester_results' boolean is a false I want to redirect the output to stderr. How can it be done?
Since you are already dealing with shell, do it in shell:
function auth_tester {
cd /data/$1/current && \
bundle exec rake 'authentication:tester' >/dev/null 2>&1
# HERE: ⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑⇑
}
stderr has an id = 2, and we are redirecting stdout to /dev/null, and stderr to stdout, eventually redirecting it to /dev/null.
To redirect stdout to stderr, use the opposite:
1>&2
To redirect the output from ruby, one uses proper receiver with IO#puts:
$stderr.puts "Goes to stderr"
Sending output to STDERR can be done via print or puts. Here I opt to send to STDERR rather than $stderr, but this will work for either:
STDERR.puts 'my message here'
If you'd like to change the exit status of your Ruby script (to show the script did not finish successfully, for example) you can use exit with a parameter of false:
exit(false)
To both send output to STDERR and return an unsuccessful exit status, you can use abort:
abort 'my message here'
For additional information, ref this article from honeybadger.io.

Capture output of shell command, line by line, into an array

I want to capture the total number of rubocop offenses to determine whether my codebase is getting better or worse. I have almost no experience with ruby scripting.
This is my script so far:
#!/usr/bin/env ruby
#script/code_coverage
var = `rubocop ../ -f fuubar -o ../coverage/rubocop_results.txt -f offenses`
puts var
So I ./rails-app/script/code_coverage and my terminal displays
...
--
6844 Total
When I var.inspect I get a long string. I want to either read the output of rubocop ../ -f ... line by line (preferred) or capture each line of output into an array.
I would like to be able to do something like this:
var.each |line| do
if line.contains('total')
...
total = ...
end
Is this possible? I guess this would be similar to writing the output to a file and and then reading the file in line by line.
If you want to keep it simple, you can simply split your var string.
var = `rubocop ../ -f fuubar -o ../coverage/rubocop_results.txt -f offenses`.split("\n")
Then you can iterate on var like you wanted to.
use open3 library of ruby. Open3 grants you access to stdin, stdout, stderr and a thread to wait the child process when running another program. You can specify various attributes, redirections, current directory, etc., of the program as Process.spawn.
http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
require 'open3'
# Run lynx on this file.
cmd = "lynx -crawl -dump /data/feed/#{file_name}.html > /data/feed/#{file_name}"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
cmdout = stdout.read
$logger.debug "stdout is:" + stdout.read
$logger.debug "stderr is:" + stderr.read
end

How can I tell from a within a shell script if the shell that invoked it is an interactive shell?

I'm trying to set up a shell script that will start a screen session (or rejoin an existing one) only if it is invoked from an interactive shell. The solution I have seen is to check if $- contains the letter "i":
#!/bin/sh -e
echo "Testing interactivity..."
echo 'Current value of $- = '"$-"
if [ `echo \$- | grep -qs i` ]; then
echo interactive;
else
echo noninteractive;
fi
However, this fails, because the script is run by a new noninteractive shell, invoked as a result of the #!/bin/sh at the top. If I source the script instead of running it, it works as desired, but that's an ugly hack. I'd rather have it work when I run it.
So how can I test for interactivity within a script?
Give this a try and see if it does what you're looking for:
#!/bin/sh
if [ $_ != $0 ]
then
echo interactive;
else
echo noninteractive;
fi
The underscore ($_) expands to the absolute pathname used to invoke the script. The zero ($0) expands to the name of the script. If they're different then the script was invoked from an interactive shell. In Bash, subsequent expansion of $_ gives the expanded argument to the previous command (it might be a good idea to save the value of $_ in another variable in order to preserve it).
From man bash:
0 Expands to the name of the shell or shell script. This is set
at shell initialization. If bash is invoked with a file of com‐
mands, $0 is set to the name of that file. If bash is started
with the -c option, then $0 is set to the first argument after
the string to be executed, if one is present. Otherwise, it is
set to the file name used to invoke bash, as given by argument
zero.
_ At shell startup, set to the absolute pathname used to invoke
the shell or shell script being executed as passed in the envi‐
ronment or argument list. Subsequently, expands to the last
argument to the previous command, after expansion. Also set to
the full pathname used to invoke each command executed and
placed in the environment exported to that command. When check‐
ing mail, this parameter holds the name of the mail file cur‐
rently being checked.
$_ may not work in every POSIX compatible sh, although it probably works in must.
$PS1 will only be set if the shell is interactive. So this should work:
if [ -z "$PS1" ]; then
echo noninteractive
else
echo interactive
fi
try tty
if tty 2>&1 |grep not ; then echo "Not a tty"; else echo "a tty"; fi
man tty :
The tty utility writes the name of the terminal attached to standard
input to standard output. The name that is written is the string
returned by ttyname(3). If the standard input is not a terminal, the
message ``not a tty'' is written.
You could try using something like...
if [[ -t 0 ]]
then
echo "Interactive...say something!"
read line
echo $line
else
echo "Not Interactive"
fi
The "-t" switch in the test field checks if the file descriptor given matches a terminal (you could also do this to stop the program if the output was going to be printed to a terminal, for example). Here it checks if the standard in of the program matches a terminal.
Simple answer: don't run those commands inside ` ` or [ ].
There is no need for either of those constructs here.
Obviously I can't be sure what you expected
[ `echo \$- | grep -qs i` ]
to be testing, but I don't think it's testing what you think it's testing.
That code will do the following:
Run echo \$- | grep -qs i inside a subshell (due to the ` `).
Capture the subshell's standard output.
Replace the original ` ` expression with a string containing that output.
Pass that string as an argument to the [ command or built-in (depending on your shell).
Produce a successful return code from [ only if that string was nonempty (assuming the string didn't look like an option to [).
Some possible problems:
The -qs options to grep should cause it to produce no output, so I'd expect [ to be testing an empty string regardless of what $- looks like.
It's also possible that the backslash is escaping the dollar sign and causing a literal 'dollar minus' (rather than the contents of a variable) to be sent to grep.
On the other hand, if you removed the [ and backticks and instead said
if echo "$-" | grep -qs i ; then
then:
your current shell would expand "$-" with the value you want to test,
echo ... | would send that to grep on its standard input,
grep would return a successful return code when that input contained the letter i,
grep would print no output, due to the -qs flags, and
the if statement would use grep's return code to decide which branch to take.
Also:
no backticks would replace any commands with the output produced when they were run, and
no [ command would try to replace the return code of grep with some return code that it had tried to reconstruct by itself from the output produced by grep.
For more on how to use the if command, see this section of the excellent BashGuide.
If you want to test the value of $- without forking an external process (e.g. grep) then you can use the following technique:
if [ "${-%i*}" != "$-" ]
then
echo Interactive shell
else
echo Not an interactive shell
fi
This deletes any match for i* from the value of $- then checks to see if this made any difference.
(The ${parameter/from/to} construct (e.g. [ "${-//[!i]/}" = "i" ] is true iff interactive) can be used in Bash scripts but is not present in Dash, which is /bin/sh on Debian and Ubuntu systems.)

Get the output value of system

I am using the following method to get the time of the video with ffmpeg do not know what reason I can not put the output of the command
command =~ /Duration: ([\d][\d]):([\d][\d]):([\d][\d]).([\d]+)/
variable for time and then insert in the
can someone give a help?
def get_time_video
command = system " ffmpeg -i video.flv 2>&1 "
command =~ /Duration: ([\d][\d]):([\d][\d]):([\d][\d]).([\d]+)/
time = " #{$1}:#{$2}:#{$3} "
puts time # 00:00:30
update_attribute(:time, “#{time}”)
end
The Kernel.system function returns true or false as seen in the Documentation. If you want to parse the output of a command, you can use the backtick notation:
system = `ffmpeg -i video.flv 2>&1`

Resources