能否从 Ruby 脚本更改 Unix/MacOS 环境变量?

huangapple go评论55阅读模式
英文:

is it possible to change the unix / macos environment variables from a ruby script

问题

我正在尝试创建一个可以从终端会话中运行的 Ruby 脚本,该脚本可以更改父进程的环境变量。我怀疑这可能是不可能的,因为它会创建一个新的进程,但我想知道是否有任何解决方法。动机是我们需要设置一组复杂的环境变量来在本地运行应用程序,并希望使用一个对每个人都可用的语言。

那么,Ruby 能做到吗?我宁愿不采取停止终端会话并重新启动的方式。

英文:

I am trying to create a ruby script that can be run from a terminal session that could alter environment variables of the parent. I suspect this isn't possible as it create a new process but curious if any workaround. The motivation is we need to set a complex list of env variables to run apps locally and want to use a language that is available to everyone.

So, can Ruby do this? I'd rather not go the path of stopping a terminal session and restarting.

答案1

得分: 2

不能修改父进程的环境

从实际角度来看,没有任何应用程序可以改变除了自己之外的任何进程的环境,而导出的值只会在子进程生成子进程的时候被继承。如果您了解足够的内核编程知识以打破这个定理,那么您就不需要提出这个问题。这意味着,就您的目的而言,您无法从子Shell或生成的进程向父进程导出变量,也无法更改已经生成的子进程的环境。

帮助其他进程修改它们自己的环境

因此,您不能直接实现您想要的功能。没有例外。

但是,如果您以这样的方式设计您的程序,使它们可以定期或在接收到信号时从文件或文件描述符中读取数据,那么您可以从生成的子进程中编辑文件,并期望某些东西会不时导入这些值。

例如,如果您有一个类似于以下的Bash程序:

while true; do
    source new_env.sh
    # 使用新值执行某些操作
    sleep 5
done

然后,每隔5秒,循环将导入new_env.sh文件,其中定义的任何导出、命令或变量将成为当前执行环境的一部分。您还可以为SIGUSR1或SIGUSR2定义一个陷阱,以在接收到信号时读取new_env.sh并在那时重新读取文件。

Ruby可以执行相同的操作。您可以定义陷阱和循环,以修改当前环境的ENV,然后会影响从那一点生成的任何子进程。您还可以序列化环境变量或其他数据,并将它们写入可以被另一个应用程序读取的位置。需要明确区分的是,这不是让Bash或Ruby直接修改另一个进程的环境;而是一种允许另一个进程自己修改的进程间通信方式。

注意事项和安全性问题

在运行时源文件是一个巨大的安全风险,通常不建议使用。您已经得到了一个可能有效的答案,但最有可能的情况是,这是您应该从架构角度考虑而不是需要找到的工程解决方法。很可能,这是您应该从架构角度考虑而不是需要找到的工程解决方法。

在运行时读取配置文件的示例

更一般地说,修改长时间运行程序中的值的正确方法是从受信任的来源(如数据库或权限受控的配置文件)读取经过白名单验证的值,然后在运行时清理输入数据。例如,您可以让您的Ruby脚本执行类似以下的操作:

require "yaml"

# 读取包含序列化的Ruby哈希的文件
# 例如:{:foo=>1, :bar=>2, :baz=>3}
def config
  # 读入您的键/值存储
  cfg = YAML.load_file "config.yml"

  # 限制哈希的允许值;
  # 不在白名单中的值(如::quux)将被忽略
  whitelist = %i[foo bar baz]

  @config.map do |key, value|
    # 只处理白名单中的键,并清理值;
    # 在这种情况下,这意味着将它们强制转换为整数,而不是接受任意值
    cfg[key] = value.to_i if whitelist.keys.include? key
  end

  # 重新导出您在@config中的任何内容,然后
  # 返回哈希
  @config.map { ENV[_1] = _2 }
end
  
loop do
  config
  # 生成一个进程、发送一个信号或使用IPC
  # 与其他某些东西通信
  sleep 5
end

这只是一个简单的示例,不是一个防弹或理想的解决方案。但是,它应该给您一个相对安全的做法的一般思路,前提是您对配置文件、白名单有控制,并且将文件的内容视为有害的,并确保在使用从中读取的任何值之前对其进行清理和验证。

无论您使用哪种编程语言都没有关系。您可以使用Ruby、Bash、Zsh或任何其他您喜欢的语言。然而,通用原则是相同的:不要源代码配置文件;只读取它,并且只处理您认为安全的行和值。

英文:

You Can't Modify the Environment of Parent Processes

Pragmatically, no application can change the environment for any process but itself, and exported values are only inherited by a child process at the time the sub-process is spawned. If you know enough about kernel programming to break that truism then you don't need to be asking the question. That means that, for your purposes, you can't export variables to a parent process from a sub-shell or spawned process, nor can you change the environment of a sub-process that has already been spawned.

Helping Other Processes Modify Their Own Environments

So, you can't do what you want directly. Period.

However, if you design your programs in such a way that you can have them read from a file or file descriptor periodically or when signaled then you can edit the file from your spawned process with the expectation that something will occasionally import these values.

For example, if you have a Bash program like:

while true; do
    source new_env.sh
    # do something with new values
    sleep 5
done

then every 5 seconds the loop will import the new_env.sh file and any exports, commands, or variables defined there would become part of the current execution environment. You could also define a trap for SIGUSR1 or SIGUSR2 to read new_env.sh when it receives the signal and have it re-read the file then.

Ruby can do the same thing. You can define traps and loops that modify ENV for the current environment, which will then impact any sub-processes spawned from that point on. You can also serialize environment variables or other data and write them somewhere that can be read by another application. As a key distinction, this isn't allowing Bash or Ruby to directly modify another process' environment; it's simply a form of inter-process communication that could allow another process to modify itself.

Caveats and Safety Concerns

Sourcing files at runtime is a huge security risk, and generally not recommended. You were given an answer that could work, but it's most probably solving the wrong problem. Most likely this is something you should look at from an architectural perspective rather than as an engineering work-around you need to find.

An Example of Reading Configuration Files at Runtime

More generally, the correct way to modify values in a long-running program would be to read whitelisted values from a trusted source like a database or permission-controlled configuration file and then sanitize the inputs at runtime. For example, you might have your Ruby scripts do something like:

require "yaml"

# read file containing a serialized Ruby Hash
# such as {:foo=>1, :bar=>2, :baz=>3}
def config
  # read in your key/value store
  cfg = YAML.load_file "config.yml"

  # limit allowed values for Hash;
  # non-whitelisted values like :quux will
  # be ignored
  whitelist = %i[foo bar baz]

  @config.map do |key, value|
    # only process whitelisted keys, and sanitize
    # the values; in this case, that means coercing
    # them to Integer rather than accepting arbitrary
    # values
    cfg[key] = value.to_i if whitelist.keys.include? key
  end

  # re-export whatever you now have in @config, and then
  # return the Hash
  @config.map { ENV[_1] = _2 }
end
  
loop do
  config
  # spawn a process, send a signal, or use IPC
  # to communicate with something else
  sleep 5
end

This is meant as a trivial example, and not intended to be a bulletproof or ideal solution. However, it should give you a general idea of how to do this relatively safely, provided you have control over your configuration file, your whitelist, and that you treat the contents of said file as tainted and ensure that you sanitize and validate any values you read from it before using them.

It doesn't matter what language you're doing this in. You can use Ruby, Bash, Zsh, or whatever else you like. The general principle is the same, though: don't source the configuration file; read it and only process the lines and values you consider safe.

huangapple
  • 本文由 发表于 2023年7月3日 23:01:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76605942.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定