Как l oop над jq уникальным массивом в bash? - PullRequest
1 голос
/ 14 февраля 2020

Я пытаюсь перебрать уникальные имена и зафиксировать сообщения из объекта 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'

Ответы [ 2 ]

6 голосов
/ 14 февраля 2020

Используйте 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

В качестве альтернативы, если у вас есть более старая версия Bash без readarray или mapfile Вы можете отделить строки с помощью управляющего символа 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
}
2 голосов
/ 14 февраля 2020

Работает с изменением '' на '_' и обратно

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' 
...