Como passar argumentos de linha de comando para uma tarefa rake

1096

Eu tenho uma tarefa de rake que precisa inserir um valor em vários bancos de dados.

Eu gostaria de passar esse valor para a tarefa rake na linha de comando ou de outra tarefa rake.

Como posso fazer isso?

Tilendor
fonte
4
rakefile rdoc
Brian Maltzan
3
Os documentos foram espelhados pelo SeattleRb.
Jonathan Allard
1
Você pode alterar a resposta aceita por @BlairAnderson, pois a resposta aceita está muito desatualizada agora. Esta questão aparece no Google no topo deste tópico!
rmcsharry

Respostas:

377

opções e dependências precisam estar dentro de matrizes:

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

Então

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

NOTA: variável taské o objeto da tarefa, não muito útil, a menos que você conheça / se preocupe com os recursos internos do Rake.

NOTA DOS TRILHOS:

Se estiver executando a tarefa a partir dos trilhos, é melhor pré-carregar o ambiente, adicionando => [:environment]qual é a maneira de configurar tarefas dependentes .

  task :work, [:option, :foo, :bar] => [:environment] do |task, args|
    puts "work", args
  end
Blair Anderson
fonte
28
Além disso, verifique se você não usa espaços entre os argumentos. Por exemplo, não fazer isso: rake thing:work[1, 2, 3]como ele não vai funcionar e você terá um erroDon't know how to build task
rpbaltazar
9
Além disso, certifique-se de incluir o argumento na string. por exemplo, a partir de sua linha de comando executar a tarefa rake como assim rake thing:work'[1,2,3]'
theterminalguy
36
Unfortuanely, zsh não pode analisar a chamada corretamente, você precisa digitar o comando em zsh assim: rake thing:work\[1,2,3\]ou estarake 'thing:work[1,2,3]'
hutusi
1
@sakurashinken, você pode remover o :environmentsímbolo da sua tarefa. trilhos aplicações têm um: tarefa ambiente ...
Blair Anderson
3
Em vez de ter uma nota a explicar que tmeios task, por que não usar taskcomo o nome param?
Joshua Pinter
1132

Você pode especificar argumentos formais no rake adicionando argumentos de símbolo à chamada de tarefa. Por exemplo:

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

Em seguida, na linha de comando:

> rake my_task [1, falso]
Os argumentos foram: {: arg1 => "1",: arg2 => "false"} da classe Rake :: TaskArguments
arg1 era: '1' da classe String
arg2 era: 'false' da classe String

> rake "minha_task [1, 2]"
Os argumentos foram: {: arg1 => "1",: arg2 => "2"}

> rake invoke_my_task
Os argumentos foram: {: arg1 => "1",: arg2 => "2"}

> rake invoke_my_task_2
Os argumentos foram: {: arg1 => 3,: arg2 => 4}

> rake com pré-requisito [5,6]
Os argumentos foram: {: arg1 => "5",: arg2 => "6"}

> rake with_defaults
Os argumentos com padrões foram: {: arg1 =>: default_1,: arg2 =>: default_2}

> rake with_defaults ['x', 'y']
Os argumentos com padrões foram: {: arg1 => "x",: arg2 => "y"}

Conforme demonstrado no segundo exemplo, se você deseja usar espaços, as aspas ao redor do nome do destino são necessárias para impedir que o shell divida os argumentos no espaço.

Observando o código em rake.rb , parece que o rake não analisa seqüências de tarefas para extrair argumentos de pré-requisitos, portanto você não pode fazer isso task :t1 => "dep[1,2]". A única maneira de especificar argumentos diferentes para um pré-requisito seria invocá-lo explicitamente na ação da tarefa dependente, como em :invoke_my_taske :invoke_my_task_2.

Observe que algumas conchas (como zsh) exigem que você escape dos colchetes: rake my_task\['arg1'\]

Nick Desjardins
fonte
5
Para chamar uma tarefa dentro de um namespace Simpy fazer: Rake :: Task [ 'namespace: tarefa'] invocar.
gaqzi
1
Essa é uma pergunta separada, Igoru, mas a razão pela qual sua chamada para executar é executada apenas uma vez é que o rake é orientado a dependências, portanto, ele só executará uma tarefa se necessário. Para tarefas genéricas, isso significa que ainda não foi executado. Para executar explicitamente uma tarefa, independentemente de suas dependências ou se for necessária, chame execute em vez de chamar.
Nick Desjardins
10
Nota: De acordo com o rake, esta sintaxe para aceitar variáveis ​​em tarefas está obsoleta:WARNING: 'task :t, arg, :needs => [deps]' is deprecated. Please use 'task :t, [args] => [deps]' instead.
Ajedi32 15/15
57
Note-se que zsh não consegue analisar os argumentos de linha de comando corretamente ( zsh: no matches found: ...), então você precisa para escapar dos colchetes: rake my_task\['arg1'\]. De robots.thoughtbot.com/post/18129303042/…
Seth Bro
2
@SethBro SIM. Se apenas o seu comentário não estivesse oculto por trás do link "Ver mais comentários", eu não teria perdido 10 minutos para fazer isso funcionar.
GMA
342

Além de responder por kch (não achei como deixar um comentário, desculpe):

Você não precisa especificar variáveis ​​como ENVvariáveis ​​antes do rakecomando. Você pode apenas defini-los como parâmetros de linha de comando usuais assim:

rake mytask var=foo

e acesse aqueles do seu arquivo rake como variáveis ​​ENV como estas:

p ENV['var'] # => "foo"
timurb
fonte
2
Esta é a melhor resposta mais simples para IMO. Funcionou imediatamente. O que exatamente psignifica?
stevec 2/06/19
1
@ user5783745 Like puts, mas em vez de registrar value.to_s na saída padrão, chama Obj.inspect e a registra na saída padrão. ruby-doc.org/core-2.0.0/Kernel.html#method-ip
kqcef
E substituir variáveis ​​de ambiente? Fantástico!
Damien Roche
O Rake é uma bagunça totalmente manipulada e esta é a única maneira que funcionou. E não sou só eu, esta resposta tem a mesma quantidade de votos que a resposta "correta".
lzap 21/01
108

Se você deseja passar argumentos nomeados (por exemplo, com padrão OptionParser), pode usar algo como isto:

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

observe o --que é necessário para ignorar os argumentos padrão do Rake. Deve trabalhar com Rake 0.9.x , <= 10.3.x .

O Rake mais recente mudou sua análise --e agora você precisa garantir que não seja passado para o OptionParser#parsemétodo, por exemplo, comparser.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 no final, garantirá que os argumentos extras não serão interpretados como tarefa Rake.

Também o atalho para argumentos deve funcionar:

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

Quando os scripts rake se parecem com isso, talvez seja hora de procurar outra ferramenta que permita isso imediatamente.

Tombart
fonte
13
Na minha perspectiva, essa é realmente a melhor resposta. Ignore kludges de variáveis ​​de ambiente, sintaxe estranha com argumentos de tarefa, o benefício adicional para o padrão --option-names. A minha única sugestão seria a utilização exit, em vez de abortcomo abortvai deixar você com um código de retorno de 1 para o shell. Se a tarefa rake fizer parte de um script de nível superior, é mais comum assumir que uma saída diferente de zero é algum tipo de erro.
31413 Joe
1
Eu concordo com Joe, esta é a melhor resposta. O natural é usar a mesma interface para passar as opções de rake, como faria ao passar as opções para um script.
Rik Smith-Unna
1
Concordo que esta é a melhor resposta. Não existe uma maneira de contornar o feio --? Como passar rakeargumentos para a tarefa real ou algo assim? Gosta task :my_task, :*args do |t, args|ou algo assim?
Augustin Riedinger
1
Além disso, não entendo para que {username}serve aqui. Onde é usado? Por que não está lá -u {username}? Cheers
Augustin Riedinger
2
A maneira como o Rake analisa o ARGV foi alterada 10.4.1e revertida 10.4.2. github.com/ruby/rake/commit/…
Tombart
58

Encontrei a resposta desses dois sites: Net Maniac e Aimred .

Você precisa ter a versão> 0.8 do rake para usar esta técnica

A descrição normal da tarefa de rake é esta:

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

Para passar argumentos, faça três coisas:

  1. Adicione os nomes dos argumentos após o nome da tarefa, separados por vírgulas.
  2. Coloque as dependências no final usando: needs => [...]
  3. Local | t, args | depois do fazer. (t é o objeto para esta tarefa)

Para acessar os argumentos no 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

Para chamar essa tarefa na linha de comando, passe os argumentos em [] s

rake task_name['Hello',4]

irá produzir

Hello
Hello
Hello
Hello

e se você quiser chamar essa tarefa de outra tarefa e passar argumentos, use invoke

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

então o comando

rake caller

irá produzir

In Caller
hi
hi

Não encontrei uma maneira de passar argumentos como parte de uma dependência, pois o código a seguir é quebrado:

task :caller => :task_name['hi',2]' do
   puts 'In Caller'
end
Tilendor
fonte
15
O formato para esta funcionalidade mudou como este estados de alerta: 'task :t, arg, :needs => [deps]' is deprecated. Please use 'task :t, [args] => [deps]' instead.
Madh
29

Outra opção comumente usada é passar variáveis ​​de ambiente. No seu código, você os lê via ENV['VAR']e pode transmiti-los antes do rakecomando, como

$ VAR=foo rake mytask
kch
fonte
Sinceramente, eu esperava que a tarefa rake - esses programas - go - to-a, e minha tarefa pudesse obtê-los do ARGV. Infelizmente eu não tenho certeza se isso é possível no entanto Atualmente, estou usando a sua solução: ancinho var1 = val1 var2 = val2
JasonSmith
3
@jhs: rake blah -- --these --go --to --a-program(observe o aviso --ao rake que seus switches terminaram), consulte stackoverflow.com/questions/5086224/…
mu é muito curto
28

Na verdade, @Nick Desjardins respondeu perfeito. Mas apenas para a educação: você pode usar uma abordagem suja: usando ENVargumentos

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

rake my_task myvar=10
#=> myvar: 10
fl00r
fonte
27

Eu não conseguia descobrir como passar argumentos e também o ambiente: até resolver isso:

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

E então eu chamo assim:

rake db:export['foo, /tmp/']
Nate Flink
fonte
24
desc 'an updated version'
task :task_name, [:arg1, :arg2] => [:dependency1, :dependency2] do |t, args|
    puts args[:arg1]
end
Feng
fonte
Para chamar isso, vá:rake task_name[hello, world]
Dex
2
from rake.rubyforge.org/files/doc/rakefile_rdoc.html "Apenas algumas palavras de cautela. O nome da tarefa rake e seus argumentos precisam ser um único argumento de linha de comando para rake. Isso geralmente significa que não há espaços. Se houver necessidade de espaço. , então toda a sequência de rake + argumento deve ser citada. Algo assim: rake "name [billy bob, smith]" ""
Gayle
19

Eu só queria poder executar:

$ rake some:task arg1 arg2

Simples, certo? (Não!)

O Rake interpreta arg1e arg2como tarefas, e tenta executá-las. Então, simplesmente abortamos antes que isso aconteça.

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

    # your task...

    exit
  end
end

Tome isso, suportes!

Disclaimer : Eu queria ser capaz de fazer isso em um projeto bem pequeno para animais de estimação. Não se destina ao uso no "mundo real", pois você perde a capacidade de encadear tarefas de varredura (ou seja rake task1 task2 task3). OMI não vale a pena. Basta usar o feio rake task[arg1,arg2].

jassa
fonte
3
Necessário para fazer isso, _, arg1, arg2 = ARGVpois o primeiro argumento foi visto como o nome da tarefa rake. Mas esse exité um truque legal.
gordos
rake task[arg1,arg2] && rake task2 && rake task3Não tenho certeza se isso é menos feio do que rake task[arg1,arg2] task2 task3. Provavelmente menos eficiente.
Nuclearman
_, *args = ARGVé perfeito para capturar todos os argumentos subseqüentes! Muito obrigado!
XtraSimplicity
12

Eu uso um argumento ruby ​​regular no arquivo rake:

DB = ARGV[1]

em seguida, elaborei as tarefas de rake na parte inferior do arquivo (pois o rake procurará uma tarefa com base no nome do argumento)

task :database_name1
task :database_name2

linha de comando:

rake mytask db_name

isso me parece mais limpo do que as soluções var = foo ENV var e a tarefa args [blah, blah2].
o esboço é um pouco instável, mas não muito ruim se você tiver apenas alguns ambientes que são uma configuração única

djburdick
fonte
2
Para evitar problemas de seqüências de caracteres congeladas, use dupno final: db = ARGV [1] .dup
Juanda
Evento melhor db = ARGV[1].dup unless ARGV[1].nil?para evitar a exceção de enganar um zero.
Andre Figueiredo
5

As maneiras de passar argumentos estão corretas na resposta acima. Entretanto, para executar tarefas de rake com argumentos, há um pequeno detalhe técnico envolvido na versão mais recente do rails

Ele funcionará com o rake "namespace: taskname ['argument1']"

Observe as aspas invertidas ao executar a tarefa na linha de comando.

Asim Mushtaq
fonte
3

Para passar argumentos para a tarefa padrão, você pode fazer algo assim. Por exemplo, diga "versão" é seu argumento:

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

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

Então você pode chamar assim:

$ rake
no version passed

ou

$ rake default[3.2.1]
version is 3.2.1

ou

$ rake build[3.2.1]
version is 3.2.1

No entanto, não encontrei uma maneira de evitar a especificação do nome da tarefa (padrão ou compilação) durante a transmissão de argumentos. Gostaria de saber se alguém sabe de uma maneira.

Garota
fonte
3

Eu gosto da sintaxe "querystring" para a passagem de argumentos, especialmente quando há muitos argumentos a serem passados.

Exemplo:

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

O "querystring" sendo:

width=10&height=20

Aviso: observe que a sintaxe é rake "mytask[foo=bar]"e NÃO rake mytask["foo=bar"]

Quando analisado dentro da tarefa rake usando Rack::Utils.parse_nested_query, obtemos um Hash:

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

(O legal é que você pode passar hashes e matrizes, mais abaixo)

Isto é como conseguir isso:

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

Aqui está um exemplo mais extenso que estou usando com o Rails na minha jóia delayed_job_active_record_threaded :

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

Analisado da mesma maneira que acima, com uma dependência de ambiente (para carregar o ambiente Rails)

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

Fornece o seguinte em options

=> {"ebooks"=>{"workers_number"=>"16", "worker_timeout"=>"60"}, "albums"=>{"workers_number"=>"32", "worker_timeout"=>"120"}}
Abdo
fonte
3

Se você não se incomoda em lembrar qual é a posição do argumento e deseja fazer algo como um hash de argumento ruby. Você pode usar um argumento para passar uma sequência e depois regexá-la em um hash de opções.

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

E na linha de comando você recebe.

$ 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
xander-miller
fonte
1
Seu código precisa de algumas linhas vazias bem colocadas. Não sei como você lê esse muro de texto.
Joshua Pinter
2

A maioria dos métodos descritos acima não funcionou para mim, talvez eles estejam obsoletos nas versões mais recentes. O guia atualizado pode ser encontrado aqui: http://guides.rubyonrails.org/command_line.html#custom-rake-tasks

uma cópia e colar do manual está aqui:

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

Invoque assim

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

Para executar tarefas de rake com o estilo de argumentos tradicionais:

rake task arg1 arg2

E então use:

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

Adicione o seguinte patch da 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
Daniel Garmoshka
fonte
-5

Ao passar parâmetros, a melhor opção é um arquivo de entrada, pode ser um excel a json ou o que for necessário e, a partir daí, leia a estrutura de dados e as variáveis ​​necessárias, incluindo o nome da variável conforme a necessidade. Ler um arquivo pode ter a seguinte estrutura.

  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

Exemplo json

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

Execução

rake :name_task 
tundervirld
fonte
4
Se você precisar de um arquivo de instruções JSON para sua tarefa Rake, provavelmente está fazendo muitas coisas na tarefa Rake.
ZiggyTheHamster 20/08/2015
Esta é maneira over-complicar algo que é incrivelmente simples.
precisa saber é o seguinte