How to pass command line arguments to a rake task

2018-12-31 14:26发布

问题:

I have a rake task that needs to insert a value into multiple databases.

I\'d like to pass this value into the rake task from the command line, or from another rake task.

How can I do this?

回答1:

options and dependencies need to be inside arrays:

namespace :thing do
  desc \"it does a thing\"
  task :work, [:option, :foo, :bar] do |task, args|
    puts \"work\", args
  end

  task :another, [:option, :foo, :bar] do |task, args|
    puts \"another #{args}\"
    Rake::Task[\"thing:work\"].invoke(args[:option], args[:foo], args[:bar])
    # or splat the args
    # Rake::Task[\"thing:work\"].invoke(*args)
  end

end

Then

rake thing:work[1,2,3]
=> work: {:option=>\"1\", :foo=>\"2\", :bar=>\"3\"}

rake thing:another[1,2,3]
=> another {:option=>\"1\", :foo=>\"2\", :bar=>\"3\"}
=> work: {:option=>\"1\", :foo=>\"2\", :bar=>\"3\"}

NOTE: variable task is the the task object, not very helpful unless you know/care about Rake internals.

RAILS NOTE:

If running the task from rails, its best to preload the environment by adding => [:environment] which is a way to setup dependent tasks.

  task :work, [:option, :foo, :bar] => [:environment] do |task, args|
    puts \"work\", args
  end


回答2:

You can specify formal arguments in rake by adding symbol arguments to the task call. For example:

require \'rake\'

task :my_task, [:arg1, :arg2] do |t, args|
  puts \"Args were: #{args}\"
end

task :invoke_my_task do
  Rake.application.invoke_task(\"my_task[1, 2]\")
end

# or if you prefer this syntax...
task :invoke_my_task_2 do
  Rake::Task[:my_task].invoke(3, 4)
end

# a task with prerequisites passes its 
# arguments to it prerequisites
task :with_prerequisite, [:arg1, :arg2] => :my_task #<- name of prerequisite task

# to specify default values, 
# we take advantage of args being a Rake::TaskArguments object
task :with_defaults, :arg1, :arg2 do |t, args|
  args.with_defaults(:arg1 => :default_1, :arg2 => :default_2)
  puts \"Args with defaults were: #{args}\"
end

Then, from the command line:

> rake my_task[1,2]
Args were: {:arg1=>\"1\", :arg2=>\"2\"}

> rake \"my_task[1, 2]\"
Args were: {:arg1=>\"1\", :arg2=>\"2\"}

> rake invoke_my_task
Args were: {:arg1=>\"1\", :arg2=>\"2\"}

> rake invoke_my_task_2
Args were: {:arg1=>3, :arg2=>4}

> rake with_prerequisite[5,6]
Args were: {:arg1=>\"5\", :arg2=>\"6\"}

> rake with_defaults
Args with defaults were: {:arg1=>:default_1, :arg2=>:default_2}

> rake with_defaults[\'x\',\'y\']
Args with defaults were: {:arg1=>\"x\", :arg2=>\"y\"}

As demonstrated in the second example, if you want to use spaces, the quotes around the target name are necessary to keep the shell from splitting up the arguments at the space.

Looking at the code in rake.rb, it appears that rake does not parse task strings to extract arguments for prerequisites, so you can\'t do task :t1 => \"dep[1,2]\". The only way to specify different arguments for a prerequisite would be to invoke it explicitly within the dependent task action, as in :invoke_my_task and :invoke_my_task_2.

Note that some shells (like zsh) require you to escape the brackets: rake my_task\\[\'arg1\'\\]



回答3:

In addition to answer by kch (I didn\'t find how to leave a comment to that, sorry):

You don\'t have to specify variables as ENV variables before the rake command. You can just set them as usual command line parameters like that:

rake mytask var=foo

and access those from your rake file as ENV variables like such:

p ENV[\'var\'] # => \"foo\"


回答4:

If you want to pass named arguments (e.g. with standard OptionParser) you could use something like this:

$ rake user:create -- --user test@example.com --pass 123

note the --, that\'s necessary for bypassing standard Rake arguments. Should work with Rake 0.9.x, <= 10.3.x.

Newer Rake has changed its parsing of --, and now you have to make sure it\'s not passed to the OptionParser#parse method, for example with parser.parse!(ARGV[2..-1])

require \'rake\'
require \'optparse\'
# Rake task for creating an account

namespace :user do |args|
  desc \'Creates user account with given credentials: rake user:create\'
  # environment is required to have access to Rails models
  task :create do
    options = {}
    OptionParser.new(args) do |opts|
      opts.banner = \"Usage: rake user:create [options]\"
      opts.on(\"-u\", \"--user {username}\",\"User\'s email address\", String) do |user|
        options[:user] = user
      end
      opts.on(\"-p\", \"--pass {password}\",\"User\'s password\", String) do |pass|
        options[:pass] = pass
      end
    end.parse!

    puts \"creating user account...\"
    u = Hash.new
    u[:email] = options[:user]
    u[:password] = options[:pass]
    # with some DB layer like ActiveRecord:
    # user = User.new(u); user.save!
    puts \"user: \" + u.to_s
    puts \"account created.\"
    exit 0
  end
end

exit at the end will make sure that the extra arguments won\'t be interpreted as Rake task.

Also the shortcut for arguments should work:

 rake user:create -- -u test@example.com -p 123

When rake scripts look like this, maybe it\'s time to look for another tool that would allow this just out of box.



回答5:

I\'ve found the answer from these two websites: Net Maniac and Aimred.

You need to have version > 0.8 of rake to use this technique

The normal rake task description is this:

desc \'Task Description\'
task :task_name => [:depends_on_taskA, :depends_on_taskB] do
  #interesting things
end

To pass arguments, do three things:

  1. Add the argument names after the task name, separated by commas.
  2. Put the dependencies at the end using :needs => [...]
  3. Place |t, args| after the do. (t is the object for this task)

To access the arguments in the script, use args.arg_name

desc \'Takes arguments task\'
task :task_name, :display_value, :display_times, :needs => [:depends_on_taskA, :depends_on_taskB] do |t, args|
  args.display_times.to_i.times do
    puts args.display_value
  end
end

To call this task from the command line, pass it the arguments in []s

rake task_name[\'Hello\',4]

will output

Hello
Hello
Hello
Hello

and if you want to call this task from another task, and pass it arguments, use invoke

task :caller do
  puts \'In Caller\'
  Rake::Task[:task_name].invoke(\'hi\',2)
end

then the command

rake caller

will output

In Caller
hi
hi

I haven\'t found a way to pass arguments as part of a dependency, as the following code breaks:

task :caller => :task_name[\'hi\',2]\' do
   puts \'In Caller\'
end


回答6:

Another commonly used option is to pass environment variables. In your code you read them via ENV[\'VAR\'], and can pass them right before the rake command, like

$ VAR=foo rake mytask


回答7:

Actually @Nick Desjardins answered perfect. But just for education: you can use dirty approach: using ENV argument

task :my_task do
  myvar = ENV[\'myvar\']
  puts \"myvar: #{myvar}\"
end 

rake my_task myvar=10
#=> myvar: 10


回答8:

I couldn\'t figure out how to pass args and also the :environment until I worked this out:

namespace :db do
  desc \'Export product data\'
  task :export, [:file_token, :file_path] => :environment do |t, args|
    args.with_defaults(:file_token => \"products\", :file_path => \"./lib/data/\")

       #do stuff [...]

  end
end

And then I call like this:

rake db:export[\'foo, /tmp/\']


回答9:

desc \'an updated version\'
task :task_name, [:arg1, :arg2] => [:dependency1, :dependency2] do |t, args|
    puts args[:arg1]
end


回答10:

I just wanted to be able to run:

$ rake some:task arg1 arg2

Simple, right? (Nope!)

Rake interprets arg1 and arg2 as tasks, and tries to run them. So we just abort before it does.

namespace :some do
  task task: :environment do
    arg1, arg2 = ARGV

    # your task...

    exit
  end
end

Take that, brackets!

Disclaimer: I wanted to be able to do this in a pretty small pet project. Not intended for \"real world\" usage since you lose the ability to chain rake tasks (i.e. rake task1 task2 task3). IMO not worth it. Just use the ugly rake task[arg1,arg2].



回答11:

I use a regular ruby argument in the rake file:

DB = ARGV[1]

then I stub out the rake tasks at the bottom of the file (since rake will look for a task based on that argument name).

task :database_name1
task :database_name2

command line:

rake mytask db_name

this feels cleaner to me than the var=foo ENV var and the task args[blah, blah2] solutions.
the stub is a little jenky, but not too bad if you just have a few environments that are a one-time setup



回答12:

The ways to pass argument are correct in above answer. However to run rake task with arguments, there is a small technicality involved in newer version of rails

It will work with rake \"namespace:taskname[\'argument1\']\"

Note the Inverted quotes in running the task from command line.



回答13:

I like the \"querystring\" syntax for argument passing, especially when there are a lot of arguments to be passed.

Example:

rake \"mytask[width=10&height=20]\"

The \"querystring\" being:

width=10&height=20

Warning: note that the syntax is rake \"mytask[foo=bar]\" and NOT rake mytask[\"foo=bar\"]

When parsed inside the rake task using Rack::Utils.parse_nested_query , we get a Hash:

=> {\"width\"=>\"10\", \"height\"=>\"20\"}

(The cool thing is that you can pass hashes and arrays, more below)

This is how to achieve this:

require \'rack/utils\'

task :mytask, :args_expr do |t,args|
  args.with_defaults(:args_expr => \"width=10&height=10\")
  options = Rack::Utils.parse_nested_query(args[:args_expr])
end

Here\'s a more extended example that I\'m using with Rails in my delayed_job_active_record_threaded gem:

bundle exec rake \"dj:start[ebooks[workers_number]=16&ebooks[worker_timeout]=60&albums[workers_number]=32&albums[worker_timeout]=120]\"

Parsed the same way as above, with an environment dependency (in order load the Rails environment)

namespace :dj do
  task :start, [ :args_expr ] => :environment do |t, args|
    # defaults here...
    options = Rack::Utils.parse_nested_query(args[:args_expr])  
  end
end

Gives the following in options

=> {\"ebooks\"=>{\"workers_number\"=>\"16\", \"worker_timeout\"=>\"60\"}, \"albums\"=>{\"workers_number\"=>\"32\", \"worker_timeout\"=>\"120\"}}


回答14:

To pass arguments to the default task, you can do something like this. For example, say \"version\" is your argument:

task :default, [:version] => [:build]

task :build, :version do |t,args|
  version = args[:version]
  puts version ? \"version is #{version}\" : \"no version passed\"
end

Then you can call it like so:

$ rake
no version passed

or

$ rake default[3.2.1]
version is 3.2.1

or

$ rake build[3.2.1]
version is 3.2.1

However, I have not found a way to avoid specifying the task name (default or build) while passing in arguments. Would love to hear if anyone knows of a way.



回答15:

Most of the methods described above did not work for me, maybe they are deprecated in the newer versions. The up-to-date guide can be found here: http://guides.rubyonrails.org/command_line.html#custom-rake-tasks

a copy-and-paste ans from the guide is here:

task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args|
  # You can use args from here
end

Invoke it like this

bin/rake \"task_name[value 1]\" # entire argument string should be quoted


回答16:

If you can\'t be bothered to remember what argument position is for what and you want do something like a ruby argument hash. You can use one argument to pass in a string and then regex that string into an options hash.

namespace :dummy_data do
  desc \"Tests options hash like arguments\"
  task :test, [:options] => :environment do |t, args|
    arg_options = args[:options] || \'\' # nil catch incase no options are provided
    two_d_array = arg_options.scan(/\\W*(\\w*): (\\w*)\\W*/)
    puts two_d_array.to_s + \' # options are regexed into a 2d array\'
    string_key_hash = two_d_array.to_h
    puts string_key_hash.to_s + \' # options are in a hash with keys as strings\'
    options = two_d_array.map {|p| [p[0].to_sym, p[1]]}.to_h
    puts options.to_s + \' # options are in a hash with symbols\'
    default_options = {users: \'50\', friends: \'25\', colour: \'red\', name: \'tom\'}
    options = default_options.merge(options)
    puts options.to_s + \' # default option values are merged into options\'
  end
end

And on the command line you get.

$ rake dummy_data:test[\"users: 100 friends: 50 colour: red\"]
[[\"users\", \"100\"], [\"friends\", \"50\"], [\"colour\", \"red\"]] # options are regexed into a 2d array
{\"users\"=>\"100\", \"friends\"=>\"50\", \"colour\"=>\"red\"} # options are in a hash with keys as strings
{:users=>\"100\", :friends=>\"50\", :colour=>\"red\"} # options are in a hash with symbols
{:users=>\"100\", :friends=>\"50\", :colour=>\"red\", :name=>\"tom\"} # default option values are merged into options


回答17:

While passing parameters, it is better option is an input file, can this be a excel a json or whatever you need and from there read the data structure and variables you need from that including the variable name as is the need. To read a file can have the following structure.

  namespace :name_sapace_task do
    desc \"Description task....\"
      task :name_task  => :environment do
        data =  ActiveSupport::JSON.decode(File.read(Rails.root+\"public/file.json\")) if defined?(data)
    # and work whit yoour data, example is data[\"user_id\"]

    end
  end

Example json

{
  \"name_task\": \"I\'m a task\",
  \"user_id\": 389,
  \"users_assigned\": [389,672,524],
  \"task_id\": 3
}

Execution

rake :name_task