How to pass command line arguments to a rake task

May 2024 ยท 10 minute read

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?

2

20 Answers

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} of class #{args.class}" puts "arg1 was: '#{args[:arg1]}' of class #{args[:arg1].class}" puts "arg2 was: '#{args[:arg2]}' of class #{args[:arg2].class}" 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,false] Args were: {:arg1=>"1", :arg2=>"false"} of class Rake::TaskArguments arg1 was: '1' of class String arg2 was: 'false' of class String > 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'\]

16

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 task object, not very helpful unless you know/care about Rake internals.

RAILS NOTE:

If running the task from Rails, it's 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 
10

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.

12

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:

  • Add the argument names after the task name, separated by commas.
  • Put the dependencies at the end using :needs => [...]
  • 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 
    1

    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/'] 
    1

    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 
    0

    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 
    5
    desc 'an updated version' task :task_name, [:arg1, :arg2] => [:dependency1, :dependency2] do |t, args| puts args[:arg1] end 
    2

    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].

    3

    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

    3

    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.

    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.

    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"}} 
    0

    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 
    0

    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 
    1

    To run rake tasks with traditional arguments style:

    rake task arg1 arg2 

    And then use:

    task :task do |_, args| puts "This is argument 1: #{args.first}" end 

    Add following patch of rake gem:

    Rake::Application.class_eval do alias origin_top_level top_level def top_level @top_level_tasks = [top_level_tasks.join(' ')] origin_top_level end def parse_task_string(string) # :nodoc: parts = string.split ' ' return parts.shift, parts end end Rake::Task.class_eval do def invoke(*args) invoke_with_call_chain(args, Rake::InvocationChain::EMPTY) end end 
    namespace :namespace1 do task :task1, [:arg1, :arg2, :arg3] => :environment do |_t, args| p args[:arg1] end end 

    calling

    rake namespace1:task1["1","2","3"]

    No need to provide environment while calling

    in zsh need to enclose calling in quotes

    'rake namespace1:task1["1","2","3"]'

    Thanks

    1

    One thing I don't see here is how to handle arbitrary arguments. If you pass arguments that are not listed in the task definition, they are still accessible under args.extras:

    task :thing, [:foo] do |task, args| puts args[:foo] # named argument puts args.extras # any additional arguments that were passed end 

    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 
    { "name_task": "I'm a task", "user_id": 389, "users_assigned": [389,672,524], "task_id": 3 } 
    rake :name_task 
    3

    ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJob2ttZ2mFcLTOsGStp12lrrS%2FjJympqWRo7FuuMinnGaZopzCrrHNrapmrJ9irm6%2BwKScZqyRqLg%3D

    ncG1vNJzZmilnJ23b63MrGpnnJmctrWty6ianpmeqL2ir8SsZZynnWS1sMOMraZmqJGowG6vzqakmqaUYrmqusRmmKufpaKyr8DSZquoZZFiv6K3xGarmqubY7W1ucs%3D