Extracting an IP address from a hosts file by hostname I am currently working with Vagrant for the first time, and therefore also working with Ruby for the first time. I have a specific hostname and IP address I would like set for my VM, both of which are stored in my hosts file. I would like to define the hostname in my Vagrantfile, and then have it automatically pull the matching IP address out of my hosts file for me. To do so, I have written the following code in my Vagrantfile:

Vagrant.configure("2") do |config|
  hostname = "vagrantvm"
  hostfile = "/etc/hosts"

  ip_address = File.foreach(hostfile).grep(/\b#{hostname}\b/) { |m| /^([0-9\.]*)\b.*/.match(m)[1] }.first

  config.vm.network :private_network, :ip =&gt; ip_address
  config.vm.hostname = hostname
  config.vm.provision :shell, :path =&gt; "bootstrap.sh"
end


Line 5 where I am assigning ip_address works, however I am not sure if it is the right way to do it. It seems a little ugly and long. I was thinking that maybe I should not put the code block passed to grep all on the same line. Or is it even necessary to have 2 separate regexes in there? Can it be done in 1 step?

It also seems strange to me that I need to add .first on the end in order to get the IP address string out of the array returned by grep. I feel like there is a better way to do this that I am missing.
You can probably get by with this:

ip = File.read(hostfile).match(/^\s*([\d\.]+)\s+\b#{hostname}\s*$/)[1]


A more "complete" way might be to do:

hosts = File.foreach(hostfile)
  .grep(/^\s*([\d\.:]+)/)
  .map { |line| line.strip.split(/\s+/).reverse }
  .to_h


Which gives you a { hostname =&gt; IP } hash for everything in the file (IPv6 included for good measure). I.e. you'd just do

ip_address = hosts[hostname]


to get the IP address.

For your current code:


  It seems a little ugly and long


It is a long line, but you can always break it up. You can chain off of the end keyword just as well as off of a }, so something like this would help readability:

File.foreach(hostfile).grep(/\b#{hostname}\b/) do |m|
  /^([0-9\.]*)\b.*/.match(m)[1]
end.first


In the 2nd approach above, I could've used grep to both match and map the lines, but I thought it nicer to separate the two.


  It also seems strange to me that I need to add .first on the end in order to get the IP address string out of the array returned by grep


grep is being called for each line, meaning it'll produce an array of matching lines. Hence why you need first. You're basically filtering and mapping every line in the file. After filtering there's (presumably!) only one line left, but it'll still be in an array.

You could use detect if you're only interested in the first matching line:

line = File.foreach(hostfile).detect { |line| line =~ %r[\b#{hostname}\b] }


That'll give you only the line containing the hostname you're looking for (or nil), which you can then parse further.