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 函数,一个操作(getsetadddel),它将使用全局变量来跟踪键及其值。适用于平面 hashes/dicts/objects,但感觉有些不尽如人意。

然后我意识到我可以使用 jq 之类的东西来(反)序列化 JSON。这也将使处理嵌套变得容易得多,而且允许不同的 dictkey 使用相同的名称而不会出现任何问题。它还将“dealing-with-environment-variables”和“dealing-with-dicts(/hashes/etc)”分开,这似乎是个好主意。这里我主要讲jqyq什么的也一样,核心点是:存储前序列化数据,读取时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}))}))}'

这将为您提供一棵只有 idtypenamenodes 的树,其中 nodes 是 [=186 的数组=],每个由children的idtypenamenodes组成,children nodes 也是一个 objects 的数组,现在只包含 idtypename。在我的例子中,它 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 的(反)序列化的一般方法=] 映射(即 dicts,hashes,objects 等),可以(也)与环境变量一起使用。作为参考,如果处理变量可能是一个很好的起点:

function dict
  switch $argv[2]
    case write
      read data
      set -xg $argv[1] "$data"
    case read, '*'
      echo $$argv[1]
  end
end

这只是简单地处理变量的读写,它值得分享的唯一原因是,首先,它允许将一些东西传递给变量,其次,它设置了一个起点来使一些东西变得更复杂,即自动将 echoed 值传递给 jq,或添加 add 操作或其他任何操作。

也可以选择编写脚本来处理该问题,而不是使用 jq。 Ruby的Marshalto_yaml似乎是个有趣的选择,因为我喜欢ruby,但每个人都有自己的喜好。对于 Python、picklepyyamljson 似乎值得一提。

值得一提的是,我不以任何方式隶属于 jq,从未贡献过,甚至没有在问题或其他方面发表过任何东西,我只是使用它,作为一个曾经在我有过的时候写脚本的人处理JSON或YAML,当我意识到它的强大时,我感到非常惊讶。