86

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.

rudolph9
  • 8,021
  • 9
  • 50
  • 80

5 Answers5

142

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.

Gizmomogwai
  • 2,496
  • 1
  • 20
  • 21
  • What do you mean by the way rake wants this to happen? – rudolph9 Jan 18 '13 at 17:46
  • 7
    The other solutions like executing with backticks and so on are no rake methods but ruby methods (see http://zhangxh.net/programming/ruby/6-ways-to-run-shell-commands-in-ruby/ for 6 different ways). What rake does do in the "sh" function is to automatically take care of the return value of the command, so that the rake task fails, if one command in sh fails. Most of the time this is just what you want. The other ways to execute commands from ruby should not as often be used from rake. – Gizmomogwai Sep 09 '13 at 11:19
  • 4
    another benefit of sh over system: sh seems to be more verbose about echo'ing commands, by default. – Luke W Nov 16 '13 at 21:32
  • 4
    I've had the best success with using sh in rake files instead of system. – orkoden Nov 19 '13 at 14:47
  • 3
    The link is dead as of July 2014, given that RubyForge is no more. – Arto Bendiken Jul 06 '14 at 22:57
  • 1
    @ArtoBendiken updated comment to include up-to-date link to rdoc. – Gizmomogwai Jul 22 '14 at 00:01
63

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:

Ben Lee
  • 52,489
  • 13
  • 125
  • 145
  • This does log the output to console, which can be a bit confusing. In rake you can use `sh "some_task"` to have the command print to terminal as if you ran it straight from terminal. – Automatico Jun 13 '15 at 01:50
7

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.

Binary Phile
  • 2,538
  • 16
  • 16
  • 1
    Thanks. This helped me by leading in the right direction. I'm using it in a ruby script where I need to call some methods that needs bash. I also use a long string with some "strange" characters. Here is how I do it, perhaps it can help someone. `system(%[xx=$(cat < – 244an Aug 13 '16 at 19:32
6

%{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
Ben Lee
  • 52,489
  • 13
  • 125
  • 145
knut
  • 27,320
  • 6
  • 84
  • 112
  • 1
    It should be noted that `%x` and backticks are actually calling the equivalent ruby kernel method, they are alternate syntax for the same exact method. – Ben Lee Mar 20 '12 at 23:13
  • 1
    `system( cmd )` worked for me. I was trying to write a pdf generator script using wkhtmltopdf. – tsega Dec 31 '12 at 07:33
1

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.

Neo li
  • 378
  • 3
  • 8