ruby system command check exit code - ruby

I have a bunch of system calls in ruby such as the following and I want to check their exit codes simultaneously so that my script exits out if that command fails.
system("VBoxManage createvm --name test1")
system("ruby test.rb")
I want something like
system("VBoxManage createvm --name test1", 0) <-- where the second parameter checks the exit code and confirms that that system call was successful, and if not, it'll raise an error or do something of that sort.
Is that possible at all?
I've tried something along the lines of this and that didn't work either.
system("ruby test.rb")
system("echo $?")
or
`ruby test.rb`
exit_code = `echo $?`
if exit_code != 0
raise 'Exit code is not zero'
end

From the documentation:
system returns true if the command gives zero exit status, false for
non zero exit status. Returns nil if command execution fails.
system("unknown command") #=> nil
system("echo foo") #=> true
system("echo foo | grep bar") #=> false
Furthermore
An error status is available in $?.
system("VBoxManage createvm --invalid-option")
$? #=> #<Process::Status: pid 9926 exit 2>
$?.exitstatus #=> 2

For me, I preferred use `` to call the shell commands and check $? to get process status. The $? is a process status object, you can get the command's process information from this object, including: status code, execution status, pid, etc.
Some useful methods of the $? object:
$?.exitstatus => return error code
$?.success? => return true if error code is 0, otherwise false
$?.pid => created process pid

system returns false if the command has an non-zero exit code, or nil if there is no command.
Therefore
system( "foo" ) or exit
or
system( "foo" ) or raise "Something went wrong with foo"
should work, and are reasonably concise.

You're not capturing the result of your system call, which is where the result code is returned:
exit_code = system("ruby test.rb")
Remember each system call or equivalent, which includes the backtick-method, spawns a new shell, so it's not possible to capture the result of a previous shell's environment. In this case exit_code is true if everything worked out, nil otherwise.
The popen3 command provides more low-level detail.

One way to do this is to chain them using and or &&:
system("VBoxManage createvm --name test1") and system("ruby test.rb")
The second call won't be run if the first fails.
You can wrap those in an if () to give you some flow-control:
if (
system("VBoxManage createvm --name test1") &&
system("ruby test.rb")
)
# do something
else
# do something with $?
end

Ruby 2.6 added option to raise exception in Kernel#system:
system("command", exception: true)

I want something like
system("VBoxManage createvm --name test1", 0) <-- where the second parameter checks the exit code and confirms that that system call was successful, and if not, it'll raise an error or do something of that sort.
You can add exception: true to your system call to have an error raised on non 0 exit codes.
For example, consider this small wrapper around system which prints the command (similar to bash -x, fails if there's a non 0 exit code (like bash -e) and returns the actual exit code:
def sys(cmd, *args, **kwargs)
puts("\e[1m\e[33m#{cmd} #{args}\e[0m\e[22m")
system(cmd, *args, exception: true, **kwargs)
return $?.exitstatus
end
To be called like: sys("hg", "update")
If you want to call a program that uses a different convention for exit codes, you can suppress raising the exception:
sys("robocopy", src, dst, "/COPYALL", "/E", "/R:0", "/DCOPY:T", exception: false)
You can also suppress stdout and stderr for noisy programs:
sys("hg", "update", "default", :out => File::NULL, :err => File::NULL)

Related

Prevent Rake's sh command from echoing the command

Whenever I call sh from rake it often echos the command that will be ran right before it is run. How can I prevent sh from logging the commands to stdout. I'd like to prevent this as I have api keys in the command I am calling, and I don't want to expose them in my build log.
There are two parts to solving this. The first is to pass the verbose: false option, which will prevent the command from being printed before it's executed:
$ cat Rakefile
SECRET = 'foobarbaz'
task :foo do
sh "echo #{SECRET} > secrets.txt", verbose: false
end
$ rake foo
(no output)
However, this doesn't help if there's an error, since Rake will print the failed command if it returns an error:
$ cat Rakefile
SECRET = 'foobarbaz'
task :foo do
sh "echo #{SECRET} > secrets.txt; exit 1", verbose: false
end
$ rake foo
rake aborted!
Command failed with status (1): [echo foobarbaz > secrets.txt; exit 1...]
...
The solution is hinted at in the docs for sh:
If a block is given, upon command completion the block is called with an OK flag (true on a zero exit status) and a Process::Status object. Without a block a RuntimeError is raised when the command exits non-zero.
You can see where the default behavior comes from in the Rake source. The solution, then, is to supply our own block:
$ cat Rakefile
SECRET = "foobarbaz"
task :foo do
sh "echo #{SECRET} > secrets.txt; exit 1", verbose: false do |ok, status|
unless ok
fail "Command failed with status (#{status.exitstatus}): [command hidden]"
end
end
end
$ rake foo
rake aborted!
Command failed with status (1): [command hidden]
...
Looks good!
If you find yourself needing this in multiple places, you could write a convenience method; something like this:
def quiet_sh(*cmd)
options = (Hash === cmd.last) ? cmd.pop : {}
options = { verbose: false }.merge(options)
sh *cmd, options do |ok, status|
unless ok
fail "Command failed with status (#{status.exitstatus}): [command hidden]"
end
end
end
SECRET = "foobarbaz"
task :foo do
quiet_sh "do_secret_things"
end

Get PowerShell output in Ruby

I am writing some automation script that needs to run PowerShell commands on a remote machine using Ruby. In Ruby I have the following code:
def run_powershell(powershell_command)
puts %Q-Executing powershell #{powershell_command}-
output = system("powershell.exe #{powershell_command}")
puts "Executed powershell output #{output}"
end
I can pass in Invoke-Command based ps1 files and everything works as expected. I can see the output in the console when I run the command.
The only problem is that there is no way to find out if the command run was successful; sometimes PowerShell is clearly throwing errors (like not able to get to the machine), but the output is always true.
Is there a way to know if the command ran successfully?
system(...) will actually return a value saying if it succeeded, not the output of the call.
So you can simply say
success = system("powershell.exe #{powershell_command}")
if success then
...
end
If you want both the output and return code, you can use `backticks` and query $? for the exit status (not the same $? as linked to in the comment to the question, by the way.)
output = `powershell.exe #{powershell_command}`
success = $?.exitstatus == 0
If you want a more reliable way that will escape things better, I'd use IO::popen
output = IO::popen(["powershell.exe", powershell_command]) {|io| io.read}
success = $?.exitstatus == 0
If the problem is that powershell itself isn't exiting with an error, you should have a look at this question
There is another option, and that is running the PowerShell from cmd. Here is the (pretty hard to figure out) syntax:
def powershell_output_true?()
ps_command = "(1+1) -eq 2"
cmd_str = "powershell -Command \" " + ps_command + " \" "
cmd = shell_out(cmd_str, { :returns => [0] })
if(cmd.stdout =~ /true/i)
Chef::Log.debug "PowerShell output is true"
return true
else
Chef::Log.debug "PowerShell output is false"
return false
end
end
I am comparing the stdout to true, but you can compare it to whatever you need.
described in blog

checking a bash command in a if statement in Ruby

How can I check the return value (true/false) of bash command in an if statement in Ruby. I want something like this to work,
if ("/usr/bin/fs wscell > /dev/null 2>&1")
has_afs = "true"
else
has_afs = "false"
end
It complains with the following error meaning, it will always return true.
(irb):5: warning: string literal in condition
What's the correct syntax ?
UPDATE :
/usr/bin/fs wscell
looks for afs installed and running condition. It will throw a string like this,
This workstation belongs to cell <afs_server_name>
If afs is not running, the command exits with status 1
You want backticks rather than double-quotes. To check a program's output:
has_afs = `/usr/bin/fs wscell > /dev/null 2>&1` == SOMETHING ? 'true' : 'false'
Where SOMETHING is filled in with what you're looking for.
You should probably use system() or Backticks and then check the exit status of the command ($?.exitstatus):
Heres a good quicktip read: http://rubyquicktips.com/post/5862861056/execute-shell-commands)
UPDATE:
system("/usr/bin/fs wscell > /dev/null 2>&1") # Returns false if command failed
has_afs = $?.exitstatus != 1 # Check if afs is running

Read STDOUT and STDERR from subprocess continiously

I'm using IO.popen to start a subprocess, but I only get the result of everything that happened in the time it took for the subprocess to run (sometimes 5 minutes or whatever) when the subprocess exits. I really need to be able to see everything the subprocess writes to stderr and stdout as-and-when it happens.
So far I could not find anything that works like this, but I'm sure it's possible.
if you need to get output in real time i would recommend to use stdlib PTY instead of popen
something like this:
require 'pty'
cmd = 'echo a; sleep 1; cat /some/file; sleep 1; echo b'
PTY.spawn cmd do |r, w, pid|
begin
r.sync
r.each_line { |l| puts "#{Time.now.strftime('%M:%S')} - #{l.strip}" }
rescue Errno::EIO => e
# simply ignoring this
ensure
::Process.wait pid
end
end
exit "#{cmd} failed" unless $? && $?.exitstatus == 0
> 33:36 - a
> 33:37 - cat: /some/file: No such file or directory
> 33:38 - b
this way you get output instantly, just as in terminal
You might want to use Open3.popen3 from standard library, it gives access to stdin, stdout, and stderr as streams.

Calling a bash script from a tcl script and returning and exit status

I am attempting to call a bash script from a TCL script and need to get exit status from the bash script or at least pass something back into the TCL script so that I can tell if my script executed successfully. Any suggestions?
See http://wiki.tcl.tk/exec -- click the "Show discussion" button -- there's a very detailed example of how to do exactly what you're asking. What you need though is catch
set status [catch {exec script.bash} output]
if {$status == 0} {
puts "script exited normally (exit status 0) and wrote nothing to stderr"
} elseif {$::errorCode eq "NONE"} {
puts "script exited normally (exit status 0) but wrote something to stderr which is in $output"
} elseif {[lindex $::errorCode 0] eq "CHILDSTATUS"} {
puts "script exited with status [lindex $::errorCode end]."
} else ...
What you want is exec the result of which will be in the return value, be warned however there are lots of gotchas using exec, particularly if you need to do any complex quoting
My experience in tcl is limited to occasional dabbling. However, following links starting with the one in #jk's answer led me to this page which discusses the errorCode variable and related things that might be useful this circumstance. Here's a quick example demonstrating the use of errorCode:
tcl:
set ret_val [catch { exec /bin/bash /path/to/bash_script }]
set errc $errorCode
set ret_val [lindex [split $errc " " ] 2]
puts $ret_val
bash_script, as referenced above:
#!/bin/bash
exit 42
which led to output of:
42

Resources