从Ruby中访问shell数组/哈希环境变量

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

Accessing shell array/hash environment variables from Ruby

问题

Ruby的ENV机制似乎不允许访问被声明为数组或关联数组(哈希)的shell环境变量(至少对于bash shell是如此)。然而,我找不到任何文档记录这个限制。

这实际上是一个硬性的、有意的限制吗?还是实际上有一种方法可以从Ruby中访问结构化的shell环境变量?

英文:

It appears that Ruby's ENV mechanism doesn't permit access to shell environment variables that are declared as arrays or associative arrays (hashes). (At least specific to the bash shell.) However, I cannot find this limitation documented anywhere.

Is this in fact a hard and intentional limitation, or is there actually a way to access structured shell environment variabled from within Ruby?

答案1

得分: 1

如@Barmar所提到的(并由他的链接https://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script中解释),这并不是Ruby的不足之处,而是bash的限制。符合POSIX标准的环境变量必须是字符串。附加的数组和关联数组构造(以及可能的数值语义)是特定于bash的附加功能,不能直接导出。有许多尝试通过序列化来保留结构属性的方法(请参阅链接),但基本答案似乎是:“如果你想这样做,你必须自己来做。没有内置的方法来实现这一点。”

英文:

As mentioned by @Barmar (and explained by his link to https://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script) this is not a deficiency in Ruby. Rather, it is a restriction in bash. POSIX-compliant envariables must be strings. The additional array and associative-array constructs (and presumably the numeric semantics as well) are bags-on-the-side specific to bash, and cannot be exported directly. There are numerous ways of attempting to retain the structure attributes through serialisation (see the link), but the basic answer appears to be: "If you want to do this, you're going to have to do it yourself. There's no built-in way of doing it."

答案2

得分: 0

我想你可以从Ruby脚本内部执行shell命令,如printenvenv,然后输出管道到一个子进程以解析所有内容或您所需的内容(根据您要查找的内容和操作系统,可能需要使用'more'标志与env命令)。

参考链接:

英文:

I'd imagine you could execute a shell command from within the Ruby script printenv or env and pipe the output to a subprocess to parse everything or what you need (may need to use the 'more' flag with the env command depending on what you're looking for and the OS).

Refs:

答案3

得分: 0

TL;DR

这个问题主要是特定于shell,并且是由于大多数shell要求环境变量必须是字符串而不是数组而引起的。在许多shell中,这使得通过Ruby的ENV模块无法访问shell数组。

从Fish导出的数组可以通过ENV作为索引数组访问,但Fish目前不明确支持关联数组。另一方面,Bash支持两种类型的数组,但似乎根本不导出数组到环境中,尽管语法允许这样做。更糟糕的是,export FOO=(foo bar baz)不会返回非零的退出状态。因此,虽然存在一些变通方法,但不能直接通过Ruby的ENV模块访问Bash数组。

如果您只想看到我首选的使用ARGV的解决方案,请跳到最后,但我认为我首先讨论的其他方法也有其自己的优点和潜在的实用价值。换句话说,如果您确实需要通过Ruby的ENV而不是ARGV进行访问,您可能需要将数组的定义传递到环境中,而不是直接访问shell数组。这就是为什么我首先讨论这些方法的原因。

Fish中的索引数组

在Fish shell中,所有变量本质上都是数组,因此您可以执行类似以下的操作:

set -gx FOO foo bar baz
ruby -e 'p ENV["FOO"]; p ENV["FOO"].split.last'

并且会得到合理的结果,但实际上需要您自己拆分字符串或了解这些shell如何编码值的方式,以找到首选的索引或将多词shell单词视为单独的数组元素。您的问题并不涉及Fish shell,但我认为它提供了一个有用的示例,即shell数组是根据shell特定的方式处理的,因此如果您 必须 通过环境变量进行操作,可能需要考虑这一点。

对于Bash和Zsh,肯定有更好的方法,而不是依赖于将shell数组直接导出到环境中,但在大多数情况下,您根本不应该依赖于Ruby的ENV来从环境中访问shell数组,因为在许多基于Bourne的shell中,这样的变量通常不能直接导出。

从Ruby中在类似Bash的Shell中使用数组

Bash和Zsh提供了declare -adeclare -Adeclare -p,这些命令可以在某种程度上帮助,但仍然需要您解决shell数组通常不会像正常字符串一样暴露在shell的环境中的问题,即使已导出,也将无法访问Ruby的ENV

例如,您可以在Bash/Zsh中执行以下操作,基本上是调用declare来重新分配存储在FOO中的数组的定义到另一个环境变量,例如RUBY_FOO。与数组本身不同,该定义可以导出到子shell和Ruby的ENV,作为间接方式来利用shell内置和扩展,以从Ruby内部访问数组元素。

FOO=(foo bar baz)
export RUBY_FOO=$(declare -p FOO)
ruby -e 'puts %x(#{ENV["RUBY_FOO"]}; printf "%s\n" ${FOO[@]})'

这将正确返回"foo\nbar\nbaz\n"。对于更复杂的单词拆分,您可以查看Shellwords模块或自行进行一些解析。例如,考虑包含每个元素两个单词的数组,除非正确引用,否则可能会导致意外的单词拆分或扩展。

FOO=("foo bar" "baz quux")
export RUBY_FOO=$(declare -p FOO)
ruby -e 'p %x(#{ENV["RUBY_FOO"]}; printf "%s\n" "${FOO[@]}").split("\n")'

这一次,您将正确获得["foo bar", "baz quux"],考虑到这种方法的局限性,这是相当不错的。您还可以手动解析declare -p的输出,将事物转换为Ruby集合,但这似乎比它值得的麻烦多了!

使用Ruby的ARGV将数组元素作为参数传递

索引数组

无论您选择的shell是什么,都有一种更简单的方法,那就是将您的索引数组简单地作为带引号的参数传递给您的脚本。例如:

FOO=("foo bar" "baz quux")
ruby -e 'ARGV.map { p _1 }' "${FOO[@]}"

这将以最小的麻烦打印出您的每个数组元素,无需间接、解析或任何其他操作,只需引用来正确形成shell中的数组值。

关联数组

请注意,对于支持关联数组的shell,非常类似的方法也适用。例如,让我们取消设置FOO并明确将其重新构造为关联数组。然后,我们将使用shell扩展将键和值传递到ARGV

unset FOO
declare -A FOO=("foo bar" 200 "baz quux" 404)
ruby -e 'size = ARGV.count / 2
         p ARGV[...size].zip(ARGV[-(size)..]).to_h' \
    "${!FOO[@]}" "${FOO[@]}"

这将返回一个漂亮的类似Hash的结果{"foo bar"=>"200", "baz quux"=>"404"},但实际上这只

英文:

TL;DR

This issue is largely shell-specific, and is casued by the fact that most shells require environment variables to be strings rather than arrays. With many shells, that makes shell arrays inaccessible via Ruby's ENV module.

Exported arrays from Fish can be accessed through ENV as indexed arrays, but Fish currently has no explicit support for associative arrays. On the other hand, Bash supports both types of arrays but doesn't appear to export arrays to the environment at all even though the syntax allows for it. To make matters worse, export FOO=(foo bar baz) won't return a non-zero exit status. As a result, you can't directly access Bash arrays through Ruby's ENV module although workarounds exist.

Skip to the end if you just want to see my preferred solution using ARGV, but I think the other approaches I discuss first have their own merits and potential utility value. In other words, if you really need access through Ruby's ENV rather than ARGV, you may need to pass the definition of the array into your environment rather than accessing the shell array directly. That's why I talk through those approaches first.

Indexed Arrays in Fish

In the Fish shell, all variables are intrinsically arrays anyway, so you can do something like:

set -gx FOO foo bar baz
ruby -e 'p ENV["FOO"]; p ENV["FOO"].split.last'

and get sensible results, but it's essentially up to you to split the strings or understand how the values are encoded by such shells to find your preferred index or treat multi-word shellwords as separate array elements. Your question wasn't about the Fish shell, but I thought it provided a useful illustration of the fact that shell arrays are (by definition) handled in shells-specific ways, so you might need to take that into account if you must do this through environment variables.

For Bash and Zsh, there are definitely better approaches than relying on the direct export of a shell array to the environment, but in most cases you simply shouldn't rely on Ruby's ENV to access shell arrays from the environment since such variables often can't be directly exported in many Bourne-based shells.

Arrays from Ruby in Bash-Like Shells

Bash and Zsh provide declare -a, declare -A, and declare -p which can help up to a point, but still require you to work around the fact that shell arrays aren't usually exposed as normal strings in the shell's environment even when exported, and will therefore be inaccessible to Ruby's ENV.

For example, you could do something like the following in Bash/Zsh to essentially call declare to re-assign the definition of the array stored in FOO to another environment variable such as RUBY_FOO. This definition, unlike the array itself, can be exported to sub-shells and Ruby's ENV as an indirect way to leverage shell builtins and expansions to access your array elements from inside Ruby.

FOO=(foo bar baz)
export RUBY_FOO=$(declare -p FOO)
ruby -e 'puts %x(#{ENV["RUBY_FOO"]}; printf "%s\n" ${FOO[@]})'

This will correctly return "foo\nbar\nbaz\n". For more complex word-splitting, you can look into the Shellwords module or do some of your own parsing. As an example, consider this array that contains two words in every element, potentially subjecting the words to unexpected word splitting or expansions unless properly quoted.

FOO=("foo bar" "baz quux")
export RUBY_FOO=$(declare -p FOO)
ruby -e 'p %x(#{ENV["RUBY_FOO"]}; printf "%s\n" "${FOO[@]}").split("\n")'

This time, you'll correct get back ["foo bar", "baz quux"], which is pretty good considering the limitations of the approach. You could also manually parse the output of declare -p to convert things into a Ruby collection, but that seems like more trouble than it's worth when there's a much easier way!

Use Ruby's ARGV to Pass Array Elements as Arguments

Indexed Arrays

A much easier approach, regardless of your choice of shell, is to simply pass your indexed array as shell-quoted arguments to your script. For example:

FOO=("foo bar" "baz quux")
ruby -e 'ARGV.map { p _1 }' "${FOO[@]}"

This will print each of your array elements with minimal fuss, and no need for indirection, parsing, or anything other than the quoting needed to properly form the shell's array values in the first place.

Associative Arrays

Note that a very similar approach also works for shells that support associative arrays. For example, let's unset FOO and explicitly recast it as an associative array. Then we'll pass the keys and values into ARGV using shell expansions.

unset FOO
declare -A FOO=("foo bar" 200 "baz quux" 404)
ruby -e 'size = ARGV.count / 2
         p ARGV[...size].zip(ARGV[-(size)..]).to_h' \
    "${!FOO[@]}" "${FOO[@]}"

This returns a pretty Hash like {"foo bar"=>"200", "baz quux"=>"404"}, but that's really just an implementation detail chosen for this example. Passing the keys and values of the associative array is easy; it's the effort of splitting, joining, and formatting the associative array's keys and values of the shell's associative array in Ruby that makes it look harder than it is. Again, that's just an implementation detail.

All we've really had to do here is pass the associative keys from the shell's "${!FOO[@]}" expansion plus the stored values from the expansion of "${FOO[@]}". The rest is just zipping up the matching key/value pairs into sub-arrays, and then converting the result to a Hash!

huangapple
  • 本文由 发表于 2023年6月29日 01:35:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76575510.html
匿名

发表评论

匿名网友

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

确定