源shell脚本进入ruby脚本中的环境

时间:2021-08-08 23:23:01

If I'm writing a shell script and I want to "source" some external (c-)shell scripts to set up my environment, I can just make calls like this:

如果我正在编写一个shell脚本并且我想“获取”一些外部(c-)shell脚本来设置我的环境,我可以像这样调用:

source /file/I/want/to/source.csh

I want to replace a shell script that does this with a ruby script. Can I do a similar thing in the ruby script?

我想用ruby脚本替换执行此操作的shell脚本。我可以在ruby脚本中做类似的事情吗?

Update:

Just tried it with test_script.csh:

刚尝试使用test_script.csh:

#!/bin/csh

setenv HAPPYTIMES True

...and test_script.rb:

#!/usr/bin/env ruby
system "~/test_script.csh"
system "echo $HAPPYTIMES"

Sadly, no HAPPYTIMES as of yet.

可悲的是,到目前为止还没有任何快乐。

6 个解决方案

#1


4  

The reason this isn't working for you is b/c ruby runs its system commands in separate shells. So when one system command finishes, the shell that had sourced your file closes, and any environment variables set in that shell are forgotten.

这不适合你的原因是b / c ruby​​在单独的shell中运行它的系统命令。因此,当一个系统命令完成时,源文件的shell将关闭,并且忘记在该shell中设置的任何环境变量。

If you don't know the name of the sourced file until runtime, then Roboprog's answer is a good approach. However, if you know the name of the sourced file ahead of time, you can do a quick hack with the hashbang line.

如果您在运行时之前不知道源文件的名称,那么Roboprog的答案是一个很好的方法。但是,如果您提前知道源文件的名称,则可以使用hashbang行快速破解。

% echo sourcer.rb
#!/usr/bin/env ruby
exec "csh -c 'source #{ARGV[0]} && /usr/bin/env ruby #{ARGV[1]}'"
% echo my-script.rb
#!/usr/bin/env ruby sourcer.rb /path/to/file/I/want/to/source.csh
puts "HAPPYTIMES = #{ENV['HAPPYTIMES']}"
% ./my-script.rb
HAPPYTIMES = True

All of these will only help you use the set enviroment variables in your ruby script, not set them in your shell (since they're forgotten as soon as the ruby process completes). For that, you're stuck with the source command.

所有这些只会帮助你在ruby脚本中使用set环境变量,而不是在shell中设置它们(因为一旦ruby过程完成就会忘记它们)。为此,你坚持使用source命令。

#2


5  

Given the following Ruby

鉴于以下Ruby

# Read in the bash environment, after an optional command.
#   Returns Array of key/value pairs.
def bash_env(cmd=nil)
  env = `#{cmd + ';' if cmd} printenv`
  env.split(/\n/).map {|l| l.split(/=/)}
end

# Source a given file, and compare environment before and after.
#   Returns Hash of any keys that have changed.
def bash_source(file)
  Hash[ bash_env(". #{File.realpath file}") - bash_env() ]
end

# Find variables changed as a result of sourcing the given file, 
#   and update in ENV.
def source_env_from(file)
  bash_source(file).each {|k,v| ENV[k] = v }
end

and the following test.sh:

和以下test.sh:

#!/usr/bin/env bash
export FOO='bar'

you should get:

你应该得到:

irb(main):019:0> source_env_from('test.sh')
=> {"FOO"=>"bar"}
irb(main):020:0> ENV['FOO']
=> "bar"

Enjoy!

#3


2  

I had have same probrem. and I resolve like below.

我有同样的问题。我解决如下。

#!/usr/local/bin/ruby

def source(filename)
  ENV.replace(eval(`tcsh -c 'source #{filename} && ruby -e "p ENV"'`))
end

p "***old env*****************************"
p ENV
source "/file/I/want/to/source.csh"
p "+++new env+++++++++++++++++++++++++++++"
p ENV

'eval' is a very powerful method. It jump over the process easily.

'eval'是一种非常强大的方法。它很容易跳过这个过程。

#4


2  

Improving a little on @takeccho's answer... Checks, and a few whistles. First, the sourced environment is cleaned via env -i, which is a safety measure but might be not desired in some cases. Second, via set -a, all variables set in the file are "exported" from the shell and thus imported into ruby. This is useful for simulating/overriding behavior found in environment files used with init scripts and systemd env files.

改善@ takeccho的答案...检查,以及几个口哨。首先,通过env -i清理源环境,这是一种安全措施,但在某些情况下可能不需要。其次,通过set -a,文件中设置的所有变量都从shell中“导出”,从而导入到ruby中。这对于在init脚本和systemd env文件中使用的环境文件中的模拟/覆盖行为很有用。

def ShSource(filename)
  # Inspired by user takeccho at http://*.com/a/26381374/3849157
  # Sources sh-script or env file and imports resulting environment
  fail(ArgumentError,"File #{filename} invalid or doesn't exist.") \
     unless File.exist?(filename)

  _newhashstr=`env -i sh -c 'set -a;source #{filename} && ruby -e "p ENV"'`
  fail(ArgumentError,"Failure to parse or process #{filename} environment")\
     unless _newhashstr.match(/^\{("[^"]+"=>".*?",\s*)*("[^"]+"=>".*?")\}$/)

  _newhash=eval(_newhashstr)
   %w[ SHLVL PWD _ ].each{|k|_newhash.delete(k) }
  _newhash.each{|k,v| ENV[k]=v } # ENV does not have #merge!
end

Theory of operation: When ruby outputs the ENV object using p, it does so in a way that ruby can read it back in as an object. So we use the shell to source the target file, and ruby (in a sub-shell) to output the environment in that serializable form. We then capture ruby's output and eval it within our ruby-process. Clearly this is not without some risk, so to mitigate the risk, we (1) validate the filename that is passed in, and (2) validate using a regexp that the thing we get back from the ruby-subshell is, in fact, a serializable hash-string. Once we're sure of that, we do the eval which creates a new hash. We then "manually" merge the hash with ENV, which is an Object and not a regular Hash. If it were a Hash, we could have used the #merge! method.

操作原理:当ruby使用p输出ENV对象时,它会以ruby可以作为对象读回来的方式这样做。因此,我们使用shell来获取目标文件,并使用ruby(在子shell中)以可序列化的形式输出环境。然后我们捕获ruby的输出并在我们的ruby过程中评估它。显然,这并非没有风险,所以为了降低风险,我们(1)验证传入的文件名,以及(2)使用正则表达式验证我们从ruby-subshel​​l返回的东西实际上是可序列化的哈希字符串。一旦我们确定了这一点,我们就会创建一个创建新哈希的eval。然后我们“手动”将哈希与ENV合并,ENV是一个Object而不是常规哈希。如果它是哈希,我们可以使用#merge!方法。

EDIT: sh -a exported things like PATH. We must also remove SHLVL and PWD before the hash-merge.

编辑:sh -a导出像PATH这样的东西。我们还必须在散列合并之前删除SHLVL和PWD。

#5


1  

You are going to have to write a function to run something like the following, and capture the output ("backtick" operation):

您将不得不编写一个函数来运行类似下面的内容,并捕获输出(“反引号”操作):

/bin/csh -e '. my_script ; env'

Loop on each line, match against something like

在每一行上循环,匹配类似的东西

/^(\w+)=(.*)$/

Then use the first match capture as the var name, and the second capture as the var value.

然后使用第一个匹配捕获作为var名称,第二个捕获作为var值。

(yes, I'm hedging on the fact that I know Perl way better than Ruby, but the approach would be the same)

(是的,我正在考虑这个事实,我知道Perl比Ruby更好,但方法会相同)

#6


-4  

system 'source /file/I/want/to/source.sh'

Not sure that this will do what you want though. It will execute the source command in a subshell. Try it and see it it does what you're after.

不确定这会做你想要的。它将在子shell中执行source命令。尝试一下,看看它做的就是你所追求的。

#1


4  

The reason this isn't working for you is b/c ruby runs its system commands in separate shells. So when one system command finishes, the shell that had sourced your file closes, and any environment variables set in that shell are forgotten.

这不适合你的原因是b / c ruby​​在单独的shell中运行它的系统命令。因此,当一个系统命令完成时,源文件的shell将关闭,并且忘记在该shell中设置的任何环境变量。

If you don't know the name of the sourced file until runtime, then Roboprog's answer is a good approach. However, if you know the name of the sourced file ahead of time, you can do a quick hack with the hashbang line.

如果您在运行时之前不知道源文件的名称,那么Roboprog的答案是一个很好的方法。但是,如果您提前知道源文件的名称,则可以使用hashbang行快速破解。

% echo sourcer.rb
#!/usr/bin/env ruby
exec "csh -c 'source #{ARGV[0]} && /usr/bin/env ruby #{ARGV[1]}'"
% echo my-script.rb
#!/usr/bin/env ruby sourcer.rb /path/to/file/I/want/to/source.csh
puts "HAPPYTIMES = #{ENV['HAPPYTIMES']}"
% ./my-script.rb
HAPPYTIMES = True

All of these will only help you use the set enviroment variables in your ruby script, not set them in your shell (since they're forgotten as soon as the ruby process completes). For that, you're stuck with the source command.

所有这些只会帮助你在ruby脚本中使用set环境变量,而不是在shell中设置它们(因为一旦ruby过程完成就会忘记它们)。为此,你坚持使用source命令。

#2


5  

Given the following Ruby

鉴于以下Ruby

# Read in the bash environment, after an optional command.
#   Returns Array of key/value pairs.
def bash_env(cmd=nil)
  env = `#{cmd + ';' if cmd} printenv`
  env.split(/\n/).map {|l| l.split(/=/)}
end

# Source a given file, and compare environment before and after.
#   Returns Hash of any keys that have changed.
def bash_source(file)
  Hash[ bash_env(". #{File.realpath file}") - bash_env() ]
end

# Find variables changed as a result of sourcing the given file, 
#   and update in ENV.
def source_env_from(file)
  bash_source(file).each {|k,v| ENV[k] = v }
end

and the following test.sh:

和以下test.sh:

#!/usr/bin/env bash
export FOO='bar'

you should get:

你应该得到:

irb(main):019:0> source_env_from('test.sh')
=> {"FOO"=>"bar"}
irb(main):020:0> ENV['FOO']
=> "bar"

Enjoy!

#3


2  

I had have same probrem. and I resolve like below.

我有同样的问题。我解决如下。

#!/usr/local/bin/ruby

def source(filename)
  ENV.replace(eval(`tcsh -c 'source #{filename} && ruby -e "p ENV"'`))
end

p "***old env*****************************"
p ENV
source "/file/I/want/to/source.csh"
p "+++new env+++++++++++++++++++++++++++++"
p ENV

'eval' is a very powerful method. It jump over the process easily.

'eval'是一种非常强大的方法。它很容易跳过这个过程。

#4


2  

Improving a little on @takeccho's answer... Checks, and a few whistles. First, the sourced environment is cleaned via env -i, which is a safety measure but might be not desired in some cases. Second, via set -a, all variables set in the file are "exported" from the shell and thus imported into ruby. This is useful for simulating/overriding behavior found in environment files used with init scripts and systemd env files.

改善@ takeccho的答案...检查,以及几个口哨。首先,通过env -i清理源环境,这是一种安全措施,但在某些情况下可能不需要。其次,通过set -a,文件中设置的所有变量都从shell中“导出”,从而导入到ruby中。这对于在init脚本和systemd env文件中使用的环境文件中的模拟/覆盖行为很有用。

def ShSource(filename)
  # Inspired by user takeccho at http://*.com/a/26381374/3849157
  # Sources sh-script or env file and imports resulting environment
  fail(ArgumentError,"File #{filename} invalid or doesn't exist.") \
     unless File.exist?(filename)

  _newhashstr=`env -i sh -c 'set -a;source #{filename} && ruby -e "p ENV"'`
  fail(ArgumentError,"Failure to parse or process #{filename} environment")\
     unless _newhashstr.match(/^\{("[^"]+"=>".*?",\s*)*("[^"]+"=>".*?")\}$/)

  _newhash=eval(_newhashstr)
   %w[ SHLVL PWD _ ].each{|k|_newhash.delete(k) }
  _newhash.each{|k,v| ENV[k]=v } # ENV does not have #merge!
end

Theory of operation: When ruby outputs the ENV object using p, it does so in a way that ruby can read it back in as an object. So we use the shell to source the target file, and ruby (in a sub-shell) to output the environment in that serializable form. We then capture ruby's output and eval it within our ruby-process. Clearly this is not without some risk, so to mitigate the risk, we (1) validate the filename that is passed in, and (2) validate using a regexp that the thing we get back from the ruby-subshell is, in fact, a serializable hash-string. Once we're sure of that, we do the eval which creates a new hash. We then "manually" merge the hash with ENV, which is an Object and not a regular Hash. If it were a Hash, we could have used the #merge! method.

操作原理:当ruby使用p输出ENV对象时,它会以ruby可以作为对象读回来的方式这样做。因此,我们使用shell来获取目标文件,并使用ruby(在子shell中)以可序列化的形式输出环境。然后我们捕获ruby的输出并在我们的ruby过程中评估它。显然,这并非没有风险,所以为了降低风险,我们(1)验证传入的文件名,以及(2)使用正则表达式验证我们从ruby-subshel​​l返回的东西实际上是可序列化的哈希字符串。一旦我们确定了这一点,我们就会创建一个创建新哈希的eval。然后我们“手动”将哈希与ENV合并,ENV是一个Object而不是常规哈希。如果它是哈希,我们可以使用#merge!方法。

EDIT: sh -a exported things like PATH. We must also remove SHLVL and PWD before the hash-merge.

编辑:sh -a导出像PATH这样的东西。我们还必须在散列合并之前删除SHLVL和PWD。

#5


1  

You are going to have to write a function to run something like the following, and capture the output ("backtick" operation):

您将不得不编写一个函数来运行类似下面的内容,并捕获输出(“反引号”操作):

/bin/csh -e '. my_script ; env'

Loop on each line, match against something like

在每一行上循环,匹配类似的东西

/^(\w+)=(.*)$/

Then use the first match capture as the var name, and the second capture as the var value.

然后使用第一个匹配捕获作为var名称,第二个捕获作为var值。

(yes, I'm hedging on the fact that I know Perl way better than Ruby, but the approach would be the same)

(是的,我正在考虑这个事实,我知道Perl比Ruby更好,但方法会相同)

#6


-4  

system 'source /file/I/want/to/source.sh'

Not sure that this will do what you want though. It will execute the source command in a subshell. Try it and see it it does what you're after.

不确定这会做你想要的。它将在子shell中执行source命令。尝试一下,看看它做的就是你所追求的。