简体   繁体   中英

How can I import environment variables from a shell script into the current Ruby environment?

I have a series of bash files that I would like to source into the current Ruby environment. Here's an example:

$ echo "export FOO=bar" > foo.sh
$ irb
> `source $(pwd)/foo.sh`
> puts ENV['FOO']
=> nil

Is there a way to source foo.sh into the parent environment without having to manually parse it?

All shell invocations in Ruby run in a subprocess, whether you use system() or backticks or Process or any other mechanism to execute them. It is not possible for a subprocess like this to effect the current Ruby process.

If you want to source a shell script prior to executing some Ruby code, you can create a wrapper:

#!/bin/sh

source foo.sh
ruby some_ruby_file.rb

If you really want to, you can try to parse out variable exports from the shell script and then set Ruby's ENV hash directly, but that's almost certainly a bad idea. It'd be hard to write, error-prone, and unmaintainable.

Either use a wrapper as above, or come up with a different way to save your environment config, such as a YAML file or some other conventional configuration solution.

TL;DR

You can't change the environment of a parent process from a child process or subshell, but you can certainly source or parse the file into the current process.

"Source" a Ruby Script

You can source a Ruby script with the Kernel#load or Kernel#require methods. That will import the contents of the file into your current process.

Parsing a Shell Script Into Ruby

If your source file is a shell script, you can't simply load it as Ruby; you will need to perform some kind of parsing of the file. This may be a security risk, unless you trust the contents, format, and source of the file you're reading in.

Assuming that you trust your input sources, and given a sample file like:

#!/usr/bin/bash

export FOO='bar'
echo FOO
echo bar
echo "FOO=$FOO"

you could do something like this:

# Find variables in the general form of "export x=y" 
env_vars = File.read('/tmp/file.txt').scan /export\s+(\S+)=(\S+)/
#=> [["FOO", "'bar'"]]

# Parse each variable into the Ruby ENV key/value pair, removing
# outer quotes on the value if present.
env_vars.each { |v| ENV[v.first] = v.last.gsub /\A['"]|['"]\Z/, '' }

# Verify you have the value you expect.
ENV['FOO']
#=> "bar"

This will add each variable found via the String#scan method into ENV , where it can then be accessed by its key.

Caveats for Parsing

  • This works fine in casual testing, but you may need to modify your regular expression if you are not exporting the variable on the same line where you define it.
  • In addition, it is up to you to sanitize or validate input.
  • I strongly recommend only setting environment variables you're expecting to see (eg use Array#select to whitelist acceptable ENV keys), and ensuring the values for each variable you set are sane and safe for your particular use case.

A Better Option for Options

In general, if you're trying to set configuration options from inside a script, you'd be better off loading a YAML file or using OptionParser . YAML files in particular are easy to read, easy to parse, human editable, and (relatively) easy to sanitize. Your mileage may vary.

Got a few almost answers, none that worked entirely for my case here's what I ended up doing:

file = Pathname.new "tmp-variables.txt"
`env -i /bin/sh -c 'source <myfile-here.sh> && env > #{file}'`
file.each_line do |line|
  line.match(/(?<key>[^=]+)=(?<value>.+)/) {|match| ENV[match[:key]] = match[:value] }
end

Here's an example script that shows it in action:

require 'pathname'

file = Pathname.new "tmp-variables.txt"
`mkdir profile.d`
`echo "export FOO=${FOO:-'bar'}" > profile.d/node`
`touch #{file}`
`env -i /bin/sh -c 'source profile.d/node && env > #{file}'`
file.each_line do |line|
  line.match(/(?<key>[^=]+)=(?<value>.+)/) {|match| ENV[match[:key]] = match[:value] }
end
puts ENV['FOO']

Some tricks here env -i executes the command ( -c ) without any environment variables. I tried using this trick How do I source environment variables for a command shell in a Ruby script? but set gives you all the environment variables, i just want the ones from that file.

Jim's idea was good, but couldn't use due to my constraints. CodeGnome was on the right path, but we cannot read the file wholesale without evaluating otherwise we mis things like file = Pathname.new "tmp-variables.txt" . Thanks all, this was quite a team effort. I've given you all up-votes.

You can import .env files using the dotenv gem.

https://github.com/bkeepers/dotenv

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM