TL;DR
You can do what you want with Kernel#eval or Object#instance_variable_set. You can also generate collections directly. If your needs are much more complex, then you may want to think about alternative data structures.
Using Eval
You are partly on the right track in that you need to define the variables in the current scope. While there are a number of ways to do this, the following will work:
Team = Struct.new :foo
for x in (1..20) do
eval "\@team#{x} = Team.new"
end
This creates your instance variables:
@team20
#=> #<struct Team foo=nil>
using interpolation.
Interpolation Without Eval
There are alternatives to Kernel#eval, of course, like Object#instance_variable_set. Consider this example:
Team = Struct.new :foo
(1..20).each { |i| instance_variable_set "@team#{i}", Team.new }
I can't really see how it makes much difference in your particular case, but this allows you to avoid #eval while still doing interpolation of the variable name.
Use a Collection
Since I can't imagine how you would use this as anything other than a collection anyway, you might as well just create a collection in the first place. For example:
Team = Struct.new :team_id
teams = (1..20).map { |i| Team.new i }
teams[9]
#=> #<struct Team team_id=10>
You can even find teams in your collection by some value, if you prefer. For example:
teams.select { |t| t.team_id == 10 }
#=> [#<struct Team team_id=10>]
None of this requires a unique variable name. You can use array position or object attributes instead, if you really need to distinguish them.