fish shell --- 如何模拟或实现哈希 table、关联数组或键值存储
fish shell --- how to simulate or implement a hash table, associative array, or key-value store
我正在从 ksh
迁移到 fish
。我发现我错过了定义关联数组、散列 table、字典或任何您想调用它的功能。有些情况可以模拟为
set dictionary$key $value
eval echo '$'dictionary$key
但是这种方法有很大的局限性;例如,$key
可能只包含字母、数字和下划线。
我明白fish
的方法是在有外部命令可用时寻找外部命令,但我有点不愿意将键值信息存储在文件系统中,即使在/run/user/<uid>
中也是如此,因为这将我限制在 "universal" 范围内。
fish
程序员如何解决缺少键值存储的问题?是否有一些我只是缺少的简单方法?
下面是我想解决的问题类型的一个示例:我想修改 fish_prompt
函数,以便某些目录不使用 prompt_pwd
打印,而是使用特殊缩写。我当然可以用 switch
命令来做到这一点,但我更希望有一个通用词典,这样我就可以查找一个目录,看看它是否有缩写。然后我可以使用 set
更改缩写,而不必编辑函数。
您可以将键存储在一个变量中,将值存储在另一个变量中,然后使用类似
的东西
if set -l index (contains -i -- foo $keys) # `set` won't modify $status, so this succeeds if `contains` succeeds
echo $values[$index]
end
检索相应的值。
其他可能性包括在一个变量中交替使用键和值,尽管遍历它很痛苦,尤其是当您尝试仅使用内置函数时。或者您可以使用分隔符并将键值对存储为一个元素,尽管这不适用于目录,因为变量不能包含 \0(这是路径唯一可能的分隔符)。
这是我实现@faho 提到的替代解决方案的方法
我使用“__”作为分隔符。
function set_field --argument-names dict key value
set -g $dict'__'$key $value
end
function get_field --argument-names dict key
eval echo $$dict'__'$key
end
如果您想将单个变量与成对的 key/values 一起使用,这是可能的,但正如@faho 提到的那样,它更复杂。方法如下:
function dict_keys -d "Print keys from a key/value paired list"
for idx in (seq 1 2 (count $argv))
echo $argv[$idx]
end
end
function dict_values -d "Print values from a key/value paired list"
for idx in (seq 2 2 (count $argv))
echo $argv[$idx]
end
end
function dict_get -a key -d "Get the value associated with a key in a k/v paired list"
test (count $argv) -gt 2 || return 1
set -l keyseq (seq 2 2 (count $argv))
# we can't simply use `contains` because it won't distinguish keys from values
for idx in $keyseq
if test $key = $argv[$idx]
echo $argv[(math $idx + 1)]
return
end
end
return 1
end
那么你可以像这样使用这些函数:
$ set -l mydict \
yellow banana \
red cherry \
green grape \
blue berry
$ dict_keys $mydict
yellow
red
green
blue
$ dict_values $mydict
banana
cherry
grape
berry
$ dict_get blue $mydict
berry
$ dict_get purple $mydict || echo "not found"
not found
我的应用程序最终需要这个,但我对内置的 fish 不太满意,所以这里是 Lua 中的一个实现:https://gist.github.com/nrnrnr/b302db5c59c600dd75c38d460423cc3d。此代码使用交替 key/value 表示:
key1 value1 key2 value2 ...
@faho 的回答让我想到了这个,我想补充一些。
起初我写了一小部分处理序列化的 fish 函数(一种库,如果你愿意的话),你会调用一个带有键名的 dict
函数,一个操作(get
、set
、add
或 del
),它将使用全局变量来跟踪键及其值。适用于平面 hash
es/dict
s/objects,但感觉有些不尽如人意。
然后我意识到我可以使用 jq
之类的东西来(反)序列化 JSON。这也将使处理嵌套变得容易得多,而且允许不同的 dict
对 key
使用相同的名称而不会出现任何问题。它还将“dealing-with-environment-variables”和“dealing-with-dicts(/hashes/etc)”分开,这似乎是个好主意。这里我主要讲jq
,yq
什么的也一样,核心点是:存储前序列化数据,读取时de-serialize,并使用一些工具来使用此类数据。
然后我开始使用 jq
重写我的函数。但是我很快意识到只使用 jq
而不使用任何函数会更容易。为了总结工作流程,让我们考虑 OP 的场景并想象我们想要使用用户文件夹的缩写,或者更好的是,我们想要为这些文件夹使用图标。为此,假设我们使用 Nerdfonts and have their icons availabe. A quick search for folders on Nerdfont's cheat sheet 显示我们只有主文件夹 (f74b
)、下载 (f74c
) 和图像 (f74e
) 的文件夹图标,所以我将使用 Material Design Icon 的“文件文档框”(f719
) 用于文档,使用 Material Design Icon 的“视频”(fa66
) 用于视频。
所以我们的代码点是:
- 用户文件夹:
\uf74b
- 下载量
\uf74c
- 图片:
\uf74e
- 文件:
\uf719
- 视频:
\ufa66
所以我们的JSON是:
{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}
出于现在将变得显而易见的原因,我将其保留在一行中。让我们使用 jq
:
来想象一下
echo '{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}' | jq
为了完整起见,这是安装了 Nerdfonts 后的样子:
现在让我们将其存储为变量:
set -g FOLDER_ICONS (echo '{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}' | jq -c)
jq -c
解释 JSON 并在 compact
结构中输出 JSON,即单行。存储变量的理想选择。
如果您需要编辑某些内容,您可以使用 jq
,也就是说您想要将文档的缩写更改为“doc”而不是图标。只需做:
set -g FOLDER_ICONS (echo $FOLDER_ICONS | jq -c '.["documents"]="doc"')
echo
部分用于读取变量,set -g
部分用于更新变量。因此,如果您不使用变量,可以忽略这些。
至于检索值,jq
显然也是这样做的。假设您想获取 documents
文件夹的缩写,您可以简单地执行以下操作:
echo $FOLDER_ICONS | jq -r '.["documents"]'
会returndoc
。如果您省略 -r
它将 return "doc"
带引号,因为字符串在 JSON.
中被引用
您也可以很容易地删除密钥,即:
set -g FOLDER_ICONS (echo $FOLDER_ICONS | jq -c 'del(."documents")')
将 set
变量 FOLDER_ICONS
读取它并将其内容传递给 jq -c 'del(."documents")'
的结果,这告诉 jq
删除密钥 "documents"
并输出 JSON 的紧凑表示,即单行。
我尝试过的所有方法都与嵌套 JSON objects 完美配合,所以这似乎是一个很好的解决方案。只需牢记操作即可:
reading .["key"]
writing .["key"]="value"
deleting del(."key")
jq
也有许多其他不错的功能,我想展示其中的一些功能,所以我尝试寻找可能适合包含在此处的内容。我使用 jq
的其中一件事是处理 wayland 的东西,尤其是 swaymsg -t get_tree
,我只是 运行 并且只有 4 个工作区和一个 window在每个中,从地狱输出 706 行 JSON(我写这篇文章时是 929,跨 5 个工作区的 6 windows,后来我关闭了 2 windows 我完成了所以我回来了此处和 re-ran 共享最低可能值的命令)。
为了给出一个更复杂的例子来说明如何使用 jq
,这里是解析 swaymsg -t get_tree
:
swaymsg -t get_tree | jq -C '{"id": .id, "type": .type, "name": .name, "nodes": (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name, "nodes": (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name}))}))}'
这将为您提供一棵只有 id
、type
、name
和 nodes
的树,其中 nodes
是 [=186 的数组=],每个由children的id
、type
、name
和nodes
组成,children nodes
也是一个 objects 的数组,现在只包含 id
、type
和 name
。在我的例子中,它 returned:
{
"id": 1,
"type": "root",
"name": "root",
"nodes": [
{
"id": 2147483646,
"type": "workspace",
"name": "__i3_scratch",
"nodes": []
},
{
"id": 184,
"type": "workspace",
"name": "1",
"nodes": []
},
{
"id": 145,
"type": "workspace",
"name": "2",
"nodes": []
},
{
"id": 172,
"type": "workspace",
"name": "3",
"nodes": [
{
"id": 173,
"type": "con",
"name": "Untitled-4 - Code - OSS"
}
]
},
{
"id": 5,
"type": "workspace",
"name": "4",
"nodes": []
}
]
}
您还可以通过稍微更改命令 jq
轻松制作扁平化版本:
swaymsg -t get_tree | jq -C '[{"id": .id, "type": .type, "name": .name}, (.nodes | map(.nodes) | flatten | map([{"id": .id, "type": .type, "name": .name}, (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name}))]))] | flatten'
现在,child 节点不再具有键 nodes
,而是也在 parent 的数组中,在我的例子中被展平:
[
{
"id": 1,
"type": "root",
"name": "root"
},
{
"id": 2147483646,
"type": "workspace",
"name": "__i3_scratch"
},
{
"id": 184,
"type": "workspace",
"name": "1"
},
{
"id": 145,
"type": "workspace",
"name": "2"
},
{
"id": 172,
"type": "workspace",
"name": "3"
},
{
"id": 173,
"type": "con",
"name": "Untitled-4 - Code - OSS"
},
{
"id": 5,
"type": "workspace",
"name": "4"
}
]
非常漂亮,不局限于环境变量,几乎解决了我能想到的所有问题。唯一的缺点是冗长,所以写一些 fish 函数来处理它可能是个好主意,但这超出了这里的范围,因为我关注的是 [=170 的(反)序列化的一般方法=] 映射(即 dict
s,hash
es,object
s 等),可以(也)与环境变量一起使用。作为参考,如果处理变量可能是一个很好的起点:
function dict
switch $argv[2]
case write
read data
set -xg $argv[1] "$data"
case read, '*'
echo $$argv[1]
end
end
这只是简单地处理变量的读写,它值得分享的唯一原因是,首先,它允许将一些东西传递给变量,其次,它设置了一个起点来使一些东西变得更复杂,即自动将 echo
ed 值传递给 jq
,或添加 add
操作或其他任何操作。
也可以选择编写脚本来处理该问题,而不是使用 jq
。 Ruby的Marshal
和to_yaml
似乎是个有趣的选择,因为我喜欢ruby,但每个人都有自己的喜好。对于 Python、pickle
、pyyaml
和 json
似乎值得一提。
值得一提的是,我不以任何方式隶属于 jq
,从未贡献过,甚至没有在问题或其他方面发表过任何东西,我只是使用它,作为一个曾经在我有过的时候写脚本的人处理JSON或YAML,当我意识到它的强大时,我感到非常惊讶。
我正在从 ksh
迁移到 fish
。我发现我错过了定义关联数组、散列 table、字典或任何您想调用它的功能。有些情况可以模拟为
set dictionary$key $value
eval echo '$'dictionary$key
但是这种方法有很大的局限性;例如,$key
可能只包含字母、数字和下划线。
我明白fish
的方法是在有外部命令可用时寻找外部命令,但我有点不愿意将键值信息存储在文件系统中,即使在/run/user/<uid>
中也是如此,因为这将我限制在 "universal" 范围内。
fish
程序员如何解决缺少键值存储的问题?是否有一些我只是缺少的简单方法?
下面是我想解决的问题类型的一个示例:我想修改 fish_prompt
函数,以便某些目录不使用 prompt_pwd
打印,而是使用特殊缩写。我当然可以用 switch
命令来做到这一点,但我更希望有一个通用词典,这样我就可以查找一个目录,看看它是否有缩写。然后我可以使用 set
更改缩写,而不必编辑函数。
您可以将键存储在一个变量中,将值存储在另一个变量中,然后使用类似
的东西if set -l index (contains -i -- foo $keys) # `set` won't modify $status, so this succeeds if `contains` succeeds
echo $values[$index]
end
检索相应的值。
其他可能性包括在一个变量中交替使用键和值,尽管遍历它很痛苦,尤其是当您尝试仅使用内置函数时。或者您可以使用分隔符并将键值对存储为一个元素,尽管这不适用于目录,因为变量不能包含 \0(这是路径唯一可能的分隔符)。
这是我实现@faho 提到的替代解决方案的方法
我使用“__”作为分隔符。
function set_field --argument-names dict key value
set -g $dict'__'$key $value
end
function get_field --argument-names dict key
eval echo $$dict'__'$key
end
如果您想将单个变量与成对的 key/values 一起使用,这是可能的,但正如@faho 提到的那样,它更复杂。方法如下:
function dict_keys -d "Print keys from a key/value paired list"
for idx in (seq 1 2 (count $argv))
echo $argv[$idx]
end
end
function dict_values -d "Print values from a key/value paired list"
for idx in (seq 2 2 (count $argv))
echo $argv[$idx]
end
end
function dict_get -a key -d "Get the value associated with a key in a k/v paired list"
test (count $argv) -gt 2 || return 1
set -l keyseq (seq 2 2 (count $argv))
# we can't simply use `contains` because it won't distinguish keys from values
for idx in $keyseq
if test $key = $argv[$idx]
echo $argv[(math $idx + 1)]
return
end
end
return 1
end
那么你可以像这样使用这些函数:
$ set -l mydict \
yellow banana \
red cherry \
green grape \
blue berry
$ dict_keys $mydict
yellow
red
green
blue
$ dict_values $mydict
banana
cherry
grape
berry
$ dict_get blue $mydict
berry
$ dict_get purple $mydict || echo "not found"
not found
我的应用程序最终需要这个,但我对内置的 fish 不太满意,所以这里是 Lua 中的一个实现:https://gist.github.com/nrnrnr/b302db5c59c600dd75c38d460423cc3d。此代码使用交替 key/value 表示:
key1 value1 key2 value2 ...
@faho 的回答让我想到了这个,我想补充一些。
起初我写了一小部分处理序列化的 fish 函数(一种库,如果你愿意的话),你会调用一个带有键名的 dict
函数,一个操作(get
、set
、add
或 del
),它将使用全局变量来跟踪键及其值。适用于平面 hash
es/dict
s/objects,但感觉有些不尽如人意。
然后我意识到我可以使用 jq
之类的东西来(反)序列化 JSON。这也将使处理嵌套变得容易得多,而且允许不同的 dict
对 key
使用相同的名称而不会出现任何问题。它还将“dealing-with-environment-variables”和“dealing-with-dicts(/hashes/etc)”分开,这似乎是个好主意。这里我主要讲jq
,yq
什么的也一样,核心点是:存储前序列化数据,读取时de-serialize,并使用一些工具来使用此类数据。
然后我开始使用 jq
重写我的函数。但是我很快意识到只使用 jq
而不使用任何函数会更容易。为了总结工作流程,让我们考虑 OP 的场景并想象我们想要使用用户文件夹的缩写,或者更好的是,我们想要为这些文件夹使用图标。为此,假设我们使用 Nerdfonts and have their icons availabe. A quick search for folders on Nerdfont's cheat sheet 显示我们只有主文件夹 (f74b
)、下载 (f74c
) 和图像 (f74e
) 的文件夹图标,所以我将使用 Material Design Icon 的“文件文档框”(f719
) 用于文档,使用 Material Design Icon 的“视频”(fa66
) 用于视频。
所以我们的代码点是:
- 用户文件夹:
\uf74b
- 下载量
\uf74c
- 图片:
\uf74e
- 文件:
\uf719
- 视频:
\ufa66
所以我们的JSON是:
{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}
出于现在将变得显而易见的原因,我将其保留在一行中。让我们使用 jq
:
echo '{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}' | jq
为了完整起见,这是安装了 Nerdfonts 后的样子:
现在让我们将其存储为变量:
set -g FOLDER_ICONS (echo '{"~":"\uf74b","downloads":"\uf74c","images":"\uf74e","documents":"\uf719","videos":"\ufa66"}' | jq -c)
jq -c
解释 JSON 并在 compact
结构中输出 JSON,即单行。存储变量的理想选择。
如果您需要编辑某些内容,您可以使用 jq
,也就是说您想要将文档的缩写更改为“doc”而不是图标。只需做:
set -g FOLDER_ICONS (echo $FOLDER_ICONS | jq -c '.["documents"]="doc"')
echo
部分用于读取变量,set -g
部分用于更新变量。因此,如果您不使用变量,可以忽略这些。
至于检索值,jq
显然也是这样做的。假设您想获取 documents
文件夹的缩写,您可以简单地执行以下操作:
echo $FOLDER_ICONS | jq -r '.["documents"]'
会returndoc
。如果您省略 -r
它将 return "doc"
带引号,因为字符串在 JSON.
您也可以很容易地删除密钥,即:
set -g FOLDER_ICONS (echo $FOLDER_ICONS | jq -c 'del(."documents")')
将 set
变量 FOLDER_ICONS
读取它并将其内容传递给 jq -c 'del(."documents")'
的结果,这告诉 jq
删除密钥 "documents"
并输出 JSON 的紧凑表示,即单行。
我尝试过的所有方法都与嵌套 JSON objects 完美配合,所以这似乎是一个很好的解决方案。只需牢记操作即可:
reading .["key"]
writing .["key"]="value"
deleting del(."key")
jq
也有许多其他不错的功能,我想展示其中的一些功能,所以我尝试寻找可能适合包含在此处的内容。我使用 jq
的其中一件事是处理 wayland 的东西,尤其是 swaymsg -t get_tree
,我只是 运行 并且只有 4 个工作区和一个 window在每个中,从地狱输出 706 行 JSON(我写这篇文章时是 929,跨 5 个工作区的 6 windows,后来我关闭了 2 windows 我完成了所以我回来了此处和 re-ran 共享最低可能值的命令)。
为了给出一个更复杂的例子来说明如何使用 jq
,这里是解析 swaymsg -t get_tree
:
swaymsg -t get_tree | jq -C '{"id": .id, "type": .type, "name": .name, "nodes": (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name, "nodes": (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name}))}))}'
这将为您提供一棵只有 id
、type
、name
和 nodes
的树,其中 nodes
是 [=186 的数组=],每个由children的id
、type
、name
和nodes
组成,children nodes
也是一个 objects 的数组,现在只包含 id
、type
和 name
。在我的例子中,它 returned:
{
"id": 1,
"type": "root",
"name": "root",
"nodes": [
{
"id": 2147483646,
"type": "workspace",
"name": "__i3_scratch",
"nodes": []
},
{
"id": 184,
"type": "workspace",
"name": "1",
"nodes": []
},
{
"id": 145,
"type": "workspace",
"name": "2",
"nodes": []
},
{
"id": 172,
"type": "workspace",
"name": "3",
"nodes": [
{
"id": 173,
"type": "con",
"name": "Untitled-4 - Code - OSS"
}
]
},
{
"id": 5,
"type": "workspace",
"name": "4",
"nodes": []
}
]
}
您还可以通过稍微更改命令 jq
轻松制作扁平化版本:
swaymsg -t get_tree | jq -C '[{"id": .id, "type": .type, "name": .name}, (.nodes | map(.nodes) | flatten | map([{"id": .id, "type": .type, "name": .name}, (.nodes | map(.nodes) | flatten | map({"id": .id, "type": .type, "name": .name}))]))] | flatten'
现在,child 节点不再具有键 nodes
,而是也在 parent 的数组中,在我的例子中被展平:
[
{
"id": 1,
"type": "root",
"name": "root"
},
{
"id": 2147483646,
"type": "workspace",
"name": "__i3_scratch"
},
{
"id": 184,
"type": "workspace",
"name": "1"
},
{
"id": 145,
"type": "workspace",
"name": "2"
},
{
"id": 172,
"type": "workspace",
"name": "3"
},
{
"id": 173,
"type": "con",
"name": "Untitled-4 - Code - OSS"
},
{
"id": 5,
"type": "workspace",
"name": "4"
}
]
非常漂亮,不局限于环境变量,几乎解决了我能想到的所有问题。唯一的缺点是冗长,所以写一些 fish 函数来处理它可能是个好主意,但这超出了这里的范围,因为我关注的是 [=170 的(反)序列化的一般方法=] 映射(即 dict
s,hash
es,object
s 等),可以(也)与环境变量一起使用。作为参考,如果处理变量可能是一个很好的起点:
function dict
switch $argv[2]
case write
read data
set -xg $argv[1] "$data"
case read, '*'
echo $$argv[1]
end
end
这只是简单地处理变量的读写,它值得分享的唯一原因是,首先,它允许将一些东西传递给变量,其次,它设置了一个起点来使一些东西变得更复杂,即自动将 echo
ed 值传递给 jq
,或添加 add
操作或其他任何操作。
也可以选择编写脚本来处理该问题,而不是使用 jq
。 Ruby的Marshal
和to_yaml
似乎是个有趣的选择,因为我喜欢ruby,但每个人都有自己的喜好。对于 Python、pickle
、pyyaml
和 json
似乎值得一提。
值得一提的是,我不以任何方式隶属于 jq
,从未贡献过,甚至没有在问题或其他方面发表过任何东西,我只是使用它,作为一个曾经在我有过的时候写脚本的人处理JSON或YAML,当我意识到它的强大时,我感到非常惊讶。