In Ruby, I'd like to access the status of the most recently executed external command's result information, that is, the information provided by $?, but would prefer a named method or variable. Is there such a thing, and, if so, how do I access it?
Process.last_status returns the same Process::Status instance as $?:
$ irb
> `ls 12341234`
ls: 12341234: No such file or directory
=> ""
> $? == Process.last_status
=> true
> $?.equal? Process.last_status
=> true
> $?.class
=> Process::Status
Thread Local
Also, this variable is thread local, so each thread will have its own variable (i.e. threads will not overwrite a single global value). From the docs at https://ruby-doc.org/core-2.5.0/Process.html#method-c-last_status:
"Returns the status of the last executed child process in the current thread."
would prefer a named […] variable.
The English standard library provides descriptive aliases for $? and other special global variables. But unlike Process.last_status this library has be loaded before any of its aliases can be used. The upside is it exists in Ruby < 2.5.
$ irb -rEnglish
> `ls 12341234`
ls: 12341234: No such file or directory
=> ""
> $? == CHILD_STATUS
=> true
> $?.equal? CHILD_STATUS
=> true
> $?.class
=> Process::Status
Related
I am new to Bash scripting, having a lot more experience with C-type languages. I have written a few scripts with a conditional that checks the value of a non-instantiated variable and if it doesn't exist or match a value sets the variable. On top of that the whole thing is in a for loop. Something like this:
for i in ${!my_array[#]}; do
if [ "${my_array[i]}" = true ]
then
#do something
else
my_array[i]=true;
fi
done
This would fail through a null pointer in Java since my_array[i] is not instantiated until after it is checked. Is this good practice in Bash? My script is working the way I designed, but I have learned that just because a kluge works now doesn't mean it will work in the future.
Thanks!
You will find this page on parameter expansion helpful, as well as this one on conditionals.
An easy way to test a variable is to check it for nonzero length.
if [[ -n "$var" ]]
then : do stuff ...
I also like to make it fatal to access a nonexisting variable; this means extra work, but better safety.
set -u # unset vars are fatal to access without exception handling
if [[ -n "${var:-}" ]] # handles unset during check
then : do stuff ...
By default, referencing undefined (or "unset") variable names in shell scripts just gives the empty string. But is an exception: if the shell is run with the -u option or set -u has been run in it, expansions of unset variables are treated as errors and (if the shell is not interactive) cause the shell to exit. Bash applies this principle to array elements as well:
$ array=(zero one two)
$ echo "${array[3]}"
$ echo "array[3] = '${array[3]}'"
array[3] = ''
$ set -u
$ echo "array[3] = '${array[3]}'"
-bash: array[3]: unbound variable
There are also modifiers you can use to control what expansions do if a variable (or array element) is undefined and/or empty (defined as the empty string):
$ array=(zero one '')
$ echo "array[2] is ${array[2]-unset}, array[3] is ${array[3]-unset}"
array[2] is , array[3] is unset
$ echo "array[2] is ${array[2]:-unset or empty}, array[3] is ${array[3]:-unset or empty}"
array[2] is unset or empty, array[3] is unset or empty
There are a bunch of other variants, see the POSIX shell syntax standard, section 2.6.2 (Parameter Expansion).
BTW, you do need to use curly braces (as I did above) around anything other than a plain variable reference. $name[2] is a reference to the plain variable name (or element 0 if it's an array), followed by the string "[2]"; ${name[2]}, on the other hand, is a reference to element 2 of the array name. Also, you pretty much always want to wrap variable references in double-quotes (or include them in double-quoted strings), to prevent the shell from "helpfully" splitting them into words and/or expanding them into lists of matching files. For example, this test:
if [ $my_array[i] = true ]
is (mostly) equivalent to:
if [ ${my_array[0]}[i] = true ]
...which isn't what you want at all. But this one:
if [ ${my_array[i]} = true ]
still doesn't work, because if my_array[i] is unset (or empty) it'll expand to the equivalent of:
if [ = true ]
...which is bad test expression syntax. You want this:
if [ "${my_array[i]}" = true ]
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)
I am using vagrant with puppet to set up virtual machines for development environments. I would like to simply set a few environment variables in the .pp file. Using virtual box and a vagrant base box for Ubuntu 64 bit.
I have this currently.
$bar = 'bar'
class foobar {
exec { 'foobar':
command => "export Foo=${bar}",
}
}
but when provisioning I get an error: Could not find command 'export'.
This seems like it should be simple enough am I missing some sort of require or path for the exec type? I noticed in the documentation there is an environment option to set up environment variables, should I be using that?
If you only need the variables available in the puppet run, whats wrong with :
Exec { environment => [ "foo=$bar" ] }
?
Simplest way to acomplish this is to put your env vars in /etc/environment, this ensures they are available to everything (or pretty much everything).
Something like this:
class example($somevar) {
file { "/etc/environment":
content => inline_template("SOMEVAR=${somevar}")
}
}
Reason for having the class parameterised is so you can target it from hiera with automatic variable lookup (http://docs.puppetlabs.com/hiera/1/puppet.html#automatic-parameter-lookup) ... if you're sticking something in /etc/environment, it's usually best if you actually make it environment specific.
note: I've only tested this on ubuntu
The way I got around it is to also use /etc/profile.d:
$bar = 'bar'
file { "/etc/profile.d/my_test.sh":
content => "export Foo=${bar}",
mode => 755
}
This ensures that everytime you login (ex ssh), the variable $MYVAR gets exported to your environment. After you apply through puppet and login (ex ssh localhost), echo $Foo would return bar
You can set an environment variable by defining it on a line in /etc/environment and you can ensure a line inside a file using file_line in puppet. Combine these two into the following solution:
file_line { "foo_env_var":
ensure => present,
line => "Foo=${bar}",
path => "/etc/environment",
}
You could try the following, which sets the environment variable for this exec:
class foobar {
exec { 'foobar' :
command => "/bin/bash -c \"export Foo=${bar}\"",
}
}
Something like this would work while preserving existing contents of the /etc/environment file:
/code/environments/{environment}/manifests/environment/variable.pp:
define profile::environment::variable (
$variable_name,
$value,
$ensure => present,
) {
file_line { $variable_name:
path => '/etc/environment',
ensure => $ensure,
line => "$variable_name=$value",
match => "$variable_name=",
}
}
Usage (in the body of a node manifest):
profile::environment::variable { 'JAVA_HOME':
variable_name => 'JAVA_HOME',
value => '/usr/lib/jvm/java-1.8.0',
}
I know this is an old question, but I was able to set the PS1 prompt value and add it to my .bashrc file like this:
$PS1 = '\[\e[0;31m\]\u\[\e[m\] \[\e[1;34m\]\w\[\e[m\] \$ '
and within a class:
exec {"vagrant-prompt":
unless => "grep -F 'export PS1=\"${PS1}\"' ${HOME_DIR}/.bashrc",
command => "echo 'export PS1=\"${PS1}\"' >> ${HOME_DIR}/.bashrc",
user => "${APP_USER}",
}
The -F makes grep it interpret it as a fixed string. Otherwise it won't find it and keeps adding to the .bashrc file.
Another variation. This has the advantage that stdlib isn't required (as is with file_line solutions), and the existing content of /etc/environment is preserved:
exec {'echo foo=bar>>/etc/environment':
onlyif => 'test -f /etc/environment',
unless => 'grep "foo=bar" /etc/environment',
path => '/usr/bin',
}
Check out the documentation https://puppet.com/docs/puppet/5.5/types/exec.html
class envcheck {
file { '/tmp/test':
ensure => file,
}
exec { 'foobar':
command => 'echo $bar >> /tmp/test',
environment => ['bar=foo'],
path => ['/bin/'],
}
}
Creating an empty file because an echo would happen in the shell Puppet is running the command in, not the one we're looking at.
Setting an environment variable bar to equal foo.
Setting the path for the echo binary, this isn't normally necessary for system commands but useful to know about.
how to pass an environment variable to a shell command that I execute using Kernel#system et al?
say, I want to run
%x{git checkout -f}
but this command relies on the environment variable $GIT_WORK_TREE. how do I set it?
You should be able to set the variable in Ruby's ENV hash prior to calling the sub-shell:
ENV['GIT_WORK_TREE'] = 'foo'
`echo $GIT_WORK_TREE`
should return "foo".
See the ENV[]= documentation for more information.
[1] (pry) main: 0> ENV['GIT_WORK_TREE'] = 'foo'
"foo"
[2] (pry) main: 0> `echo $GIT_WORK_TREE`
"foo\n"
You can use Process.spawn to set the environment:
spawn({'GIT_WORK_TREE' => '/foo/bar'}, "git checkout -f")
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