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
Related
I want to call Ruby function from command line with array as an argument.
Script name is test.rb
In below code Environments are like test,dev,uat.', am passing as ['test','dev','uat']
I have tried as below:
ruby -r "./test.rb" -e "start_services '['dev','test','uat']','developer','welcome123'"
def start_services(environments,node_user_name,node_password)
environments.each do |env|
puts env
end
puts node_user_name
puts node_password
end
Output:
-e:1: syntax error, unexpected tIDENTIFIER, expecting end-of-input start_services '['dev','test','uat']','developer',' ^
You clearly want to pass an array as the first parameter into start_services method, so you should do it like this:
$ ruby -r "./test.rb" -e "start_services ['dev','test','uat'],'developer','welcome123'"
# output:
dev
test
uat
developer
welcome123
What you've been trying so far was attempt to pass '[\'dev\',\'test\',\'uat\']' string, which was malformed, because you didn't escape ' characters.
Don't pass your credentials as arguments, any user on your system would be able to see them.
Instead, you could save them as environment variables or in a config file.
if ARGV.size == 0
puts "Here's how to launch this script : ruby #{__FILE__} env_name1 env_name2 ..."
exit
end
# Define those environment variables before launching the script.
# Alternative : write credentials in a json or yml file.
node_username = ENV["NODE_USERNAME"]
node_password = ENV["NODE_PASSWORD"]
ARGV.each do |env|
puts "Launching environment #{env}"
end
I want to fetch a npm package and untar it in a groovy script like so:
def cmd = "cd .composerpages/umanagement && npm pack #mag-umanagement/umanagement-pages-v2810#^28.10.4-SNAPSHOT && tar xvzf *.tgz"
cmd.execute()
Unfortunately, it executes only the first term (cd .composerpages/umanagement).
Is there a way to have multiple commands executed in one shell process?
If you need all the "shell-isms" there, then just let the shell handle it (with -c). E.g.:
def cmd = "cd .composerpages/umanagement && npm pack #mag-umanagement/umanagement-pages-v2810#^28.10.4-SNAPSHOT && tar xvzf *.tgz"
["/bin/sh", "-c", cmd].execute()
I think you need to execute all the options and not && them.
This is how you should approach it:
def cmd = 'cd .composerpages/umanagement'.execute() | 'npm pack #mag-umanagement/umanagement-pages-v2810#^28.10.4-SNAPSHOT'.execute() | 'tar xvzf *.tgz'.execute()
cmd.waitFor()
println cmd.text
In this case you can try tokenizing your pipeline of commands to a list of commands and execute them in sequence as long as they return exit code 0 (&& stops command pipeline when the command returns nonzero exit code). Consider following example:
def cmd = 'echo test && echo foo && exit 1 && echo 123'
cmd.tokenize('&&').every {
try {
def p = it.execute()
def output = p.text.trim()
p.waitFor()
println output
return p.exitValue() == 0
} catch (e) {
return false
}
}
Here we have a pipeline of 4 commands:
echo test
echo foo
exit 1
echo 123
Chaining these commands with AND operator (&&) expects stopping the pipeline after exit 1.
Groovy's Iterable.every(Closure closure) method executes as long as returned predicate is true. In our case we continue iterating over the list of commands as long as exit code is 0.
Running above example produces following output to console:
test
foo
Given a file with Ruby 2.3.0p0:
#!/usr/bin/env ruby
# frozen_string_literal: true
# Exit cleanly from an early interrupt
Signal.trap("INT") { abort }
This is fine.
# frozen_string_literal: true
#!/usr/bin/env ruby
# Exit cleanly from an early interrupt
Signal.trap("INT") { abort }
will result in error:
syntax error near unexpected token `"INT"'
`Signal.trap("INT") { abort }'
Why?
A shebang has to appear on the file's initial line.
A file test.rb containing:
#!/usr/bin/env ruby
# foo bar
puts "hello from #{RbConfig.ruby}"
will be run via Ruby:
$ ./test.rb
hello from /.../ruby-2.3.0/bin/ruby
But if test.rb contains: (1st and 2nd line swapped)
# foo bar
#!/usr/bin/env ruby
echo "hello from $SHELL"
it will be run as an ordinary shell script:
$ ./test.rb
hello from /.../bin/zsh
Therefore, the error you are getting is no Ruby error, it's from your shell.
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 have a Rakefile like this
task :clean do
sh 'rm ./foo'
end
I want to prevent it from reporting error when the file 'foo' does not exist. How to do that?
I think what I want is: Is there a way to check the file first and then decide what to do next.
For example:
file 'aaa' => 'bbb' do
sh 'cp bbb aaa'
end
This task depends on the existence of file 'bbb', so I want to know can I tell Rake that my task depends on the non-existence of file 'foo' ?
You can do this by extending rake a bit:
Rakefile:
require File.join(File.dirname(__FILE__), 'unfile_rake_ext')
unfile 'target.txt' do
File.delete('target.txt')
end
unfile_rake_ext.rb:
class UnFileTask < Rake::FileTask
def needed?
File.exist?(name)
end
end
def unfile(*args, &block)
UnFileTask.define_task(*args, &block)
end
And my console output:
D:\Projects\ZPersonal\tmp>ls
Rakefile unfile_rake_ext.rb
D:\Projects\ZPersonal\tmp>touch target.txt && ls
Rakefile target.txt unfile_rake_ext.rb
D:\Projects\ZPersonal\tmp>rake target.txt --trace
** Invoke target.txt (first_time)
** Execute target.txt
D:\Projects\ZPersonal\tmp>ls
Rakefile unfile_rake_ext.rb
D:\Projects\ZPersonal\tmp>rake target.txt --trace
** Invoke target.txt (first_time, not_needed)
D:\Projects\ZPersonal\tmp>ls
Rakefile unfile_rake_ext.rb
Hope this helps.
In your rakefile:
task :clean do
rm 'foo' if File.exists? 'foo'
end
file 'aaa' => ['bbb', :clean] do |t|
cp t.prerequisites[0], t.name
end
Now at the command line:
echo 'test' > bbb
rake aaa
=> cp bbb aaa
touch foo
rake aaa
=> rm foo
=> cp bbb aaa
How about this?
if File.exists? './foo/'
sh 'rm -f ./foo'
end