3

Having a simple rake task:

task :ws, [:str] => :environment do |t, args|
  puts args.str.inspect
end

I get following results when run this task in command line:

$ rake ws[' ']
nil

$ rake ws['    ']
nil

$ rake ws['    1']
"    1"

$ rake ws['    1    ']
"    1"

$ rake 'ws[ ]'
nil

$ rake 'ws[    ]'
nil

$ rake 'ws[    1]'
"    1"

$ rake 'ws[    1    ]'
"    1"

While when the task is being invoked in rails console everything works as expected:

2.3.3 :258 > Rake::Task['ws'].invoke(' ')
" "
2.3.3 :259 > Rake::Task['ws'].reenable
2.3.3 :260 > Rake::Task['ws'].invoke('    ')
"    "
2.3.3 :261 > Rake::Task['ws'].reenable
2.3.3 :262 > Rake::Task['ws'].invoke('    1')
"    1"
2.3.3 :263 > Rake::Task['ws'].reenable
2.3.3 :264 > Rake::Task['ws'].invoke('    1    ')
"    1    "

Again, it is definitely not the OS who is responsible for that trimming, since it shouldn't trim anything between quote marks. And besides it can be easily tested this way:

task :ws, [:str] => :environment do |t, args|
  puts args.str.inspect
  puts ARGV[1].inspect
end

then in command line:

$ rake 'ws[  1  ]' '  2  '
"  1"
"  2  "

Can trailing whitespaces be somehow preserved when task is being run in command line ? Without using command line parameters (' 2 ' in the example above) since Rake will try to execute a task for each such parameter and will raise an error if a task is not found, or even worse if it finds a task then it will execute it.

Rake version 12.0.0

trushkevich
  • 2,657
  • 1
  • 28
  • 37

1 Answers1

1

You can escape a single space character at the end of your whitespace, like so:

rake 'ws[   2  \ ]'
"   2   "

This seems to work how you would want.

The backslash is passed along as part of the argument and the backslash-space pair is getting interpreted as a space (by design or accident?) in another layer.

Take a look at Rake::Application#parse_task_string. You can experiment yourself in irb, e.g.

Rake::Application.new.parse_task_string('ws[   1   ]')
=> ["ws", ["   1"]]

Rake::Application.new.parse_task_string('ws[   1  \ ]')
=> ["ws", ["   1   "]] 

I actually want to step through this to see what's going on.

First it splits your task name and arguments like so:

/^([^\[]+)(?:\[(.*)\])$/ =~ 'ws[    1    ]'

Now we have "ws" in $1 and " 1 " in $2. All good so far. $2 becomes remaining_args. remaining_args then gets chopped up by repeated passes by another regex:

/((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args # Starts as "    1    "

And now $1 is " 1"! That \s* is eating any continuous whitespace at the end of any single argument.

So why does the backspace preserve those spaces?

  1. A single space character is permitted to survive after the backspace (captured by the \\. in the capture group). Also, everything up to the backspace is captured.

  2. The argument string is then passed to .gsub(/\\(.)/, '\1'). This replaces backspace-anything with anything. So the backspace-space pair is replaced with space. I wonder why they don't just .gsub(/\\/, '')? It must be to allow escaped backslashes.

The current implementation will additionally preserve a backspace that happens to be the last char at the end of an argument, for whatever reason. As a side effect it seems that ending a single argument with a backslash (even an escaped backslash) will turn the whole argument into an empty string, e.g.

Rake::Application.new.parse_task_string('task[tears in rain\]')
=>["task", [""]]

So that's fun (if you need to pass along a Windows directory for example).

Rake::Application.new.parse_task_string('task[C:\\Johnny\\Likes\\Windows\\]')
=>["task", [""]]

I guess the author's intention is to allow you to escape the comma to avoid splitting the argument string. It has the side effect of allowing you to pass whitespace. I wouldn't rely on it though...

struthersneil
  • 2,700
  • 10
  • 11
  • 1
    Nice finding about escaping the last space character. And yes, I also checked yesterday `Rake::Application#parse_task_string`, created an issue also https://github.com/ruby/rake/issues/216. It's obvious that something is wrong with the regexp they use. Whatever the intention was it is definitely not normal that the behavior in command line differs from the behavior when you run a task via `Rake::Task['my_task'].invoke(arg)`. Anyway, apart from fixing the bug, I suppose the best solution that can exist is to escape the last whitespace, so marking the answer as accepted. – trushkevich Aug 11 '17 at 08:04
  • 1
    Another option I would consider is not to trust this (possibly accidental) solution at all, and just `base64` your arguments and decode them on the other side, e.g. `rake "ws[$(echo ' 2 ' | base64)]"`. At least you'll know the solution won't be bugfixed away someday by a tweak to that regex (there are no tests asserting it works this way that I can see). Depends on your specific circumstances of course. – struthersneil Aug 11 '17 at 13:43
  • In my case I will stick to the 1st way since I'm the only user who will run the task (the app is a helper for myself and nobody else is going to use it) and the 1st way is just much easier and faster to type, and when the bug is fixed then I'll just switch to a normal use. But in general if somebody else reads this post, then yes, it is also a nice option to take into consideration. – trushkevich Aug 11 '17 at 16:11