如何编写委托给 jq 的 fish 函数?

How to write a fish function that delegates to jq?

我正在尝试编写一个名为 jq_select_keys 的 fish 函数,它从给定的 JSON 中选择键的子集。

执行此魔术的 jq 咒语是:

jq -r "with_entries(select([.key] | inside([\"bar\",\"baz\",\"qux\"])))" file.json

现在我正在尝试定义一个名为 jq_select_keys 的便捷函数,它将获取我感兴趣的文件名和键,并吐出子集。这是我想出的:

function jq_select_keys --description 'Selects given keys from json input'
  set key_names (for key in $argv[2..-1]; echo "\\"$key\\""; end)
  set key_names_joined (string join "," $key_names)
  set jq_args "\"with_entries(select([.key] | inside([$key_names_joined])))\""
  echo "Command: jq -r $jq_args $argv[1]"
  jq -r $jq_args $argv[1]
end

当我 运行 jq_select_keys foo.json bar baz qux 我的鱼 shell 时,我得到以下输出:

Command: jq -r "with_entries(select([.key] | inside([\"bar\",\"baz\",\"qux\"])))" foo.json
with_entries(select([.key] | inside(["bar","baz","qux"])))

现在,有趣的一点是我可以复制粘贴 echo 语句的输出,它 运行 符合预期。但是我得到的输出只是我传递给 jq 的查询字符串。

我是 shell 编程的新手,所以我可能搞砸了我的报价。但除此之外,我不知道如何让这个东西工作!

jq 是一只有趣的野兽。我不知道为什么你的脚本只输出 $jq_args 内容。

它有一个 --slurpfile 选项,可以将 JSON 字符串的文件读入数组变量。这使您免于动态构建 JSON 数组和 jq 脚本主体

$ function jqs
        jq -r --slurpfile keys (printf '"%s"\n' $argv[2..-1] | psub) \
            'with_entries(select([.key] | inside($keys)))' $argv[1]
end    

$ cat file.json
{"foo":"a","bar":"b","baz":"c"}

$ jqs file.json foo baz
{
  "foo": "a",
  "baz": "c"
}

I am new to shell programming, so I might have messed up my quotes.

基本上就是这样。

看来你加了一层引号太多了。

在 fish(以及大多数其他 shell 中,包括例如 bash 和 zsh),引号仅在按字面使用时对 shell 有特殊意义。

这意味着当变量包含 "some string" 时,那么 echo $variable 将打印 "some string" - 这意味着 echo 命令接收到字符串 with引号 (注意:bash 也会在此处应用分词,因此它实际上会收到 "somestring",即使您可能认为它被引用)。

我建议你一个一个地检查你的函数,然后简单地 echo 变量。然后假设您将 那个确切的字符串 传递给了 jq - 它会起作用吗?

例如$key_names 位:

set key_names (for key in $argv[2..-1]; echo "\\"$key\\""; end)

这样做的目的显然是转义所有键(从第二个开始的参数)以供jq使用。这意味着它们需要被引用一次。它们不需要被第二次引用,因为 shell 不关心非文字引号。

所以任何键都应该看起来像 "key"

但是当我们把

printf '%s\n' $key_names

(这将在其自己的行上打印每个键)

在此之后,我们看到

\"bar\"
\"baz\"
\"qux\"

那是 层引用!引号本身,以及转义它们的反斜杠。

所以让我们删除一个:

set key_names (for key in $argv[2..-1]; echo "\"$key\""; end)

这将导致 "bar""baz""qux"

(这可以通过使用 fish 的 cartesian product 简化为 set key_names \"$argv[2..-1]\"

接下来是:

set jq_args "\"with_entries(select([.key] | inside([$key_names_joined])))\""

你的意图是运行这就好像你有

jq -r "with_entries(select([.key] | inside([\"bar\",\"baz\",\"qux\"])))" foo.json

在命令行上。但是当你使用变量时,你不需要再次引用其中的字符串 - 一旦赋值,所有的拆分和其他扩展都已经发生,并且当变量被替换时不会再次发生。

所以只需删除转义引号

set jq_args "with_entries(select([.key] | inside([$key_names_joined])))"

你的函数应该可以工作。

结果是:

function jq_select_keys --description 'Selects given keys from json input'
    set key_names \"$argv[2..-1]\"
    set key_names_joined (string join "," $key_names)
    set jq_args "with_entries(select([.key] | inside([$key_names_joined])))"
    echo "Command: jq -r $jq_args $argv[1]"
    jq -r $jq_args $argv[1]
end