【问题标题】:How to loop over jq unique array in bash?如何在bash中循环jq唯一数组?
【发布时间】:2020-02-14 12:44:20
【问题描述】:

我正在尝试遍历唯一名称并提交来自 github json 对象的消息。但是,当数组中有空格时,bash 会将它们视为单独的数组项

#!/usr/bin/env bash

commits='[
  {
    "author": {
      "email": "email@example.com",
      "name": "Chris",
      "username": "chris"
    },
    "committer": {
      "email": "email@example.com",
      "name": "Chris",
      "username": "chris"
    },
    "message": "commit message 1"
  },
  {
    "author": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "committer": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "message": "commit message 2"
  },
    {
    "author": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "committer": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "message": "commit message 3"
  }
]'

authors=$( jq -rc '[.[].author.name] | unique | @sh' <<<"${commits}" )
echo "authors: $authors"

# this works
for author in $authors
do
  echo "author: $author"
done

echo "------------"

# this does not
messages=$( jq -rc '[.[].message] | unique | @sh' <<<"${commits}" )
echo "messages: $messages"

for message in $messages
do
  echo "message: $message"
done

哪些输出

authors: 'Chris' 'John'
author: 'Chris'
author: 'John'
------------
messages: 'commit message 1' 'commit message 2' 'commit message 3'
message: 'commit
message: message
message: 1'
message: 'commit
message: message
message: 2'
message: 'commit
message: message
message: 3'

虽然我期待:

authors: 'Chris' 'John'
author: 'Chris'
author: 'John'
------------
messages: 'commit message 1' 'commit message 2' 'commit message 3'
message: 'commit message 1'
message: 'commit message 2'
message: 'commit message 3'

【问题讨论】:

  • 这能回答你的问题吗? Use Bash to read line by line and keep space
  • 如果是这样,我不确定如何......它没有使用来自jq 的数组,而且我没有逐行阅读。但我对 bash 相当糟糕,所以你永远不知道......
  • readarray -d '' messages &lt; &lt;(jq -j '.[].message + "\u0000"' &lt;&lt;&lt;"${commits}")

标签: bash jq


【解决方案1】:

使用readarray(Bash 4+)映射来自jq的空分隔输出:

#!/usr/bin/env bash

commits='[
  {
    "author": {
      "email": "email@example.com",
      "name": "Chris",
      "username": "chris"
    },
    "committer": {
      "email": "email@example.com",
      "name": "Chris",
      "username": "chris"
    },
    "message": "commit message 1"
  },
  {
    "author": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "committer": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "message": "commit message 2"
  },
    {
    "author": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "committer": {
      "email": "email@example.com",
      "name": "John",
      "username": "jdoe"
    },
    "message": "commit message 3"
  }
]'

readarray -d '' authors < <(jq -j '.[].author.name + "\u0000"' <<<"${commits}")

for author in "${authors[@]}"
do
  echo "author: $author"
done

echo "------------"

readarray -d '' messages < <(jq -j '.[].message + "\u0000"' <<<"${commits}")

for message in "${messages[@]}"
do
  echo "message: $message"
done

或者,如果您有没有 readarraymapfile 的旧 Bash 版本,您可以使用 ASCII 控制字符 ETX(TeXt 结尾 03)分隔字符串并使用 read 代替,如下所示:

IFS=$'\03' read -d '' -ra authors < <(jq -j '.[].author.name + "\u0003"' <<<"${commits}")

IFS=$'\03' read -d '' -ra messages < <(jq -j '.[].message + "\u0003"' <<<"${commits}")

也可以通过单个 jq 调用填充两个数组:

# Populates both arrays from a single jq call
{
  IFS=$'\03' read -r -d '' -a authors
  IFS=$'\03' read -r -d '' -a messages
} < <(jq -j '([.[].author.name] | unique | .[] + "\u0003"), "\u0000",  ([.[].message] | unique | .[] + "\u0003")' <<<"${commits}")

解释:

  • [.[].author.name] | unique | .[] + "\u0003":
    输出以ETX (03) 分隔的唯一作者姓名列表。

  • "\u0000":插入空分隔符

  • [.[].message] | unique | .[] + "\u0003":
    输出以ETX (03) 分隔的唯一消息列表。

  • jq 的整个输出提供给具有两个read 命令的命令组。
    每个read 将停止在空分隔符或流的末尾。

{
  IFS=$'\03' read -r -d '' -a authors
  IFS=$'\03' read -r -d '' -a messages
}

【讨论】:

  • 非常感谢!在第一部分中,我认为您遗漏了独特的部分 readarray -d '' authors &lt; &lt;(jq -j '[.[].author.name] | unique | .[] + "\u0000"' &lt;&lt;&lt;"${commits}") 似乎有效。但是,IFS 更整洁,ty!
【解决方案2】:

适用于将 ' ' 更改为 '_' 并返回

messages=$( jq -rc '[.[].message] | unique | @sh' <<<"${commits}" )
messages="${messages// /_}"
messages=(${messages//"'_'"/"' '"})
echo "messages: ${messages[@]//_/ }"
for message in "${messages[@]//_/ }"
do
  echo " message: $message"
done

或者像这样

IFS=$'\n' messages=( $(jq -rc '.[].message' <<<"${commits}") )
printf   "messages: "; printf "'%s' " "${messages[@]}"; echo
printf   " message: '%s' \n"          "${messages[@]}"

我们可以做类似的事情

     IFS=$'\n'
 authors=($(jq -rc '.[].author.name' <<<"${commits}"))
messages=($(jq -rc '.[].message'     <<<"${commits}"))
printf " authors | "; printf "'%s' " "${authors[@]}" ; echo
printf "  author | '%s' \n"          "${authors[@]}"
echo   "---------+---------"
printf "messages | "; printf "'%s' " "${messages[@]}"; echo
printf " message | '%s' \n"          "${messages[@]}"

这样输出

 authors | 'Chris' 'John' 'John' 
  author | 'Chris' 
  author | 'John' 
  author | 'John' 
---------+---------
messages | 'commit message 1' 'commit message 2' 'commit message 3' 
 message | 'commit message 1' 
 message | 'commit message 2' 
 message | 'commit message 3' 

【讨论】:

  • 感谢您的投稿!这适用于我的具体示例,但如果用户使用下划线提交带有变量名的消息,它将被剥离。我想你可以使用__~~__ 之类的东西,而不是希望没有人会使用该字符序列,但似乎在 jq/bash 之间必须有一种更简单的方法。如果事实并非如此,我将标记为答案:)
  • 同意注意事项,但 ++ 最好使用此处发布的//_/(曾经?!)。另外,我建议不要使用~,因为它在 *nixLand 中作为 metachar 有多种用途。祝大家好运。
  • 谢谢@Ivan,IFS 路线完美运行。还回答了我的下一个问题,即如何不输出引号,非常感谢:)
猜你喜欢
  • 1970-01-01
  • 2022-09-30
  • 2016-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多