【问题标题】:Source shell script into environment within a ruby script将 shell 脚本源到 ruby​​ 脚本中的环境中
【发布时间】:2009-07-28 23:09:04
【问题描述】:

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

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

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

更新:

刚刚用 test_script.csh 试了一下:

#!/bin/csh

setenv HAPPYTIMES True

...和 ​​test_script.rb:

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

遗憾的是,目前还没有 HAPPYTIMES。

【问题讨论】:

标签: ruby shell scripting environment


【解决方案1】:

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

def ShSource(filename)
  # Inspired by user takeccho at http://stackoverflow.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

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

编辑:sh -a 导出了 PATH 之类的东西。我们还必须在哈希合并之前删除SHLVLPWD

【讨论】:

    【解决方案2】:

    鉴于以下 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
    

    还有下面的test.sh:

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

    你应该得到:

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

    享受吧!

    【讨论】:

      【解决方案3】:

      这对您不起作用的原因是 b/c ruby​​ 在单独的 shell 中运行其 system 命令。因此,当一个系统命令完成时,为您提供文件的 shell 将关闭,并且该 shell 中设置的任何环境变量都将被遗忘。

      如果您直到运行时才知道源文件的名称,那么Roboprog's answer 是一个好方法。但是,如果您提前知道源文件的名称,则可以使用 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
      

      所有这些只会帮助您在 ruby​​ 脚本中使用设置的环境变量,而不是在 shell 中设置它们(因为一旦 ruby​​ 进程完成,它们就会被遗忘)。为此,您只能使用 source 命令。

      【讨论】:

      • 我猜这在 csh 中起作用的唯一原因是您使用相同的 shell 实例来解释您正在采购的脚本。所以你不能从 bash 脚本中获取 .csh 文件,就像你不能从 ruby​​ 脚本中获取它一样......
      【解决方案4】:

      我也有同样的问题。我像下面这样解决。

      #!/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' 是一个非常强大的方法。 它很容易跳过这个过程。

      【讨论】:

      • 如果filename 确实是您所期望的,您需要非常小心;如果这是用户输入,那么这是一个巨大的安全漏洞......您通常不想获取任意文件,因此更安全的方法可能是假设 filename 是一个符号,并在Hash(如果不存在,则抛出错误)。
      【解决方案5】:
      system 'source /file/I/want/to/source.sh'
      

      不确定这是否会满足您的要求。它将在子shell 中执行源命令。尝试一下,看看它是否符合您的要求。

      【讨论】:

      • 我试过这个,但它似乎没有做我想要的。我正在尝试将 shell 脚本设置的环境变量放入运行 ruby​​ 脚本的环境中。
      • 子进程中的环境变化不会影响父进程
      • 我认为可能是这样。很抱歉浪费了您的时间。
      【解决方案6】:

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

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

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

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

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

      (是的,我对 Perl 的了解远胜于 Ruby,但方法是一样的)

      【讨论】:

      • 实际上,我在运行脚本后捕获了所有环境变量。如果“脚本”中的分配没有插值,只有字符串文字,你可以得到你想要的只是读取文件,而不必在管道上运行它(反引号)。
      猜你喜欢
      • 2014-03-29
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      • 1970-01-01
      • 2014-09-30
      • 1970-01-01
      相关资源
      最近更新 更多