Как объединить массивы YAML?

113

Я хотел бы объединить массивы в YAML и загрузить их через рубин -

some_stuff: &some_stuff
 - a
 - b
 - c

combined_stuff:
  <<: *some_stuff
  - d
  - e
  - f

Я хотел бы иметь объединенный массив как [a,b,c,d,e,f]

Я получаю сообщение об ошибке: не нашел ожидаемого ключа при разборе сопоставления блоков

Как объединить массивы в YAML?

lfender6445
источник
6
Почему вы хотите сделать это в YAML, а не на языке, на котором вы его анализируете?
Патрик Коллинз
7
чтобы высушить дублирование в очень большом файле
yaml
4
Это очень плохая практика. Вы должны читать yamls отдельно, собирать массивы вместе в Ruby, а затем записывать их обратно в yaml.
sawa
75
Как пытается быть сухой плохой практикой?
krak3n 09
13
@PatrickCollins Я нашел этот вопрос, пытаясь уменьшить дублирование в моем файле .gitlab-ci.yml, и, к сожалению, у меня нет контроля над парсером, который использует GitLab CI :(
rink.attendant. 6

Ответы:

42

Если целью является запуск последовательности команд оболочки, вы можете добиться этого следующим образом:

# note: no dash before commands
some_stuff: &some_stuff |-
    a
    b
    c

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Это эквивалентно:

some_stuff: "a\nb\nc"

combined_stuff:
  - "a\nb\nc"
  - d
  - e
  - f

Я использовал это на своем gitlab-ci.yml(чтобы ответить на комментарий @ rink.attendant.6 по вопросу).


Рабочий пример, который мы используем для поддержки requirements.txtчастных репозиториев из gitlab:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://git@gitlab.com"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts

test:
    image: python:3.7.3
    stage: test
    script:
        - *pip_git
        - pip install -q -r requirements_test.txt
        - python -m unittest discover tests

use the same `*pip_git` on e.g. build image...

где requirements_test.txtсодержит, например,

-e git+ssh://git@gitlab.com/example/example.git@v0.2.2#egg=example

Хорхе Лейтао
источник
3
Умная. Сейчас я использую его в нашем конвейере Bitbucket. Спасибо
Dariop
* Конечный тире здесь не требуется, достаточно только трубы в конце. * Это худшее решение, поскольку при сбое задания в очень длинном многострочном операторе неясно, какая команда завершилась ошибкой.
Мина Люк
1
@MinaLuke, хуже по сравнению с чем? Ни один из текущих ответов не предоставляет способ объединить два элемента, используя только yaml ... Более того, в вопросе нет ничего о том, что OP желает использовать это в CI / CD. Наконец, когда это используется в CI / CD, ведение журнала зависит только от конкретного используемого CI / CD, а не от объявления yaml. Так что, во всяком случае, CI / CD, о котором вы говорите, делает плохую работу. Ямл в этом ответе действителен и решает проблему OP.
Хорхе Лейтао
@JorgeLeitao Я думаю, вы используете его для объединения правил. Можете ли вы предоставить рабочий пример gitlabci? Я пробовал что-то на основе вашего решения, но всегда получаю ошибку проверки.
niels
1
у меня это не работает. с - я получаю сообщение об ошибке, как будто он пытается вставить список в элемент списка. Я не знаю, как пользоваться трубкой. для чего это было? как @Dariop удается использовать его в BB Pipelines?
Виктор Феррейра
26

Обновление: 2019-07-01 14:06:12

  • Примечание : другой ответ на этот вопрос был существенно отредактирован с обновлением альтернативных подходов .
    • В этом обновленном ответе упоминается альтернатива обходному пути в этом ответе. Он был добавлен в раздел См. Также ниже.

Контекст

Этот пост предполагает следующий контекст:

  • питон 2.7
  • парсер YAML на Python

Проблема

lfender6445 хочет объединить два или более списков в файле YAML, и чтобы эти объединенные списки отображались как один единственный список при синтаксическом анализе.

Решение (обходной путь)

Это может быть получено просто путем присвоения привязок YAML сопоставлениям, где желаемые списки появляются как дочерние элементы сопоставлений. Однако здесь есть предостережения (см. «Ловушки» ниже).

В приведенном ниже примере у нас есть три сопоставления ( list_one, list_two, list_three) и три якоря и псевдонима, которые ссылаются на эти сопоставления, где это необходимо.

Когда файл YAML загружается в программу, мы получаем список, который нам нужен, но может потребоваться небольшая модификация после загрузки (см. Подводные камни ниже).

пример

Исходный файл YAML

  list_one: & id001
   - а
   - б
   - с

  list_two: & id002
   - е
   - ж
   - грамм

  list_three: & id003
   - ч
   - я
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Результат после YAML.safe_load

## list_combined
  [
    [
      "а",
      "б",
      "c"
    ],
    [
      "е",
      "е",
      "грамм"
    ],
    [
      "час",
      "я",
      "j"
    ]
  ]

Ловушки

  • этот подход создает вложенный список списков, который может не соответствовать желаемому результату, но его можно обработать с помощью метода сглаживания.
  • в обычные предостережения YAML якорей и псевдонимы применяются для уникальности и порядка декларирования

Вывод

Этот подход позволяет создавать объединенные списки с помощью функции псевдонима и привязки YAML.

Хотя выходной результат представляет собой вложенный список списков, его можно легко преобразовать с помощью flattenметода.

Смотрите также

Обновленный альтернативный подход @Anthon

Примеры flattenметода

Dreftymac
источник
21

Это не сработает:

  1. слияние поддерживается только спецификациями YAML для сопоставлений, но не для последовательностей

  2. вы полностью смешиваете вещи, имея ключ слияния, << за которым следует разделитель ключ / значение :и значение, которое является ссылкой, а затем продолжаете со списком на том же уровне отступа

Это не правильный YAML:

combine_stuff:
  x: 1
  - a
  - b

Таким образом, ваш пример синтаксиса даже не имеет смысла в качестве предложения по расширению YAML.

Если вы хотите сделать что-то вроде объединения нескольких массивов, вы можете рассмотреть такой синтаксис, как:

combined_stuff:
  - <<: *s1, *s2
  - <<: *s3
  - d
  - e
  - f

где s1, s2, s3являются якорями на последовательностях (не показаны) , которые вы хотите объединить в новую последовательность и затем иметь d, eи f приложенные к этому. Но YAML сначала определяет глубину такого рода структур, поэтому во время обработки ключа слияния нет реального контекста. У вас нет доступного массива / списка, к которому вы могли бы прикрепить обработанное значение (закрепленную последовательность).

Вы можете использовать подход, предложенный @dreftymac, но это имеет огромный недостаток, заключающийся в том, что вам нужно каким-то образом знать, какие вложенные последовательности нужно сгладить (т.е. зная «путь» от корня загруженной структуры данных до родительской последовательности), или что вы рекурсивно просматриваете загруженную структуру данных в поисках вложенных массивов / списков и без разбора сглаживаете их все.

Лучшим решением IMO было бы использовать теги для загрузки структур данных, которые выполняют сглаживание за вас. Это позволяет четко обозначать, что нужно развернуть, а что нет, и дает вам полный контроль над тем, выполняется ли это выравнивание во время загрузки или во время доступа. Какой из них выбрать, зависит от простоты реализации и эффективности использования времени и места для хранения. Это тот же компромисс, который необходимо сделать для реализации ключевой функции слияния, и не существует единого решения, которое всегда было бы лучшим.

Например, моя ruamel.yamlбиблиотека использует слияния грубой силы во время загрузки при использовании своего безопасного загрузчика, что приводит к объединению словарей, которые являются обычными dicts Python. Это слияние должно выполняться заранее и дублирует данные (неэффективное использование пространства), но при этом выполняется быстрый поиск значений. При использовании загрузчика туда и обратно вы хотите иметь возможность выгружать слияния без объединения, поэтому их нужно хранить отдельно. Такая структура данных, как dict, загруженная в результате загрузки туда и обратно, занимает мало места, но имеет более медленный доступ, поскольку ему нужно попытаться найти ключ, не найденный в самом dict при слияниях (и он не кэшируется, поэтому он нужно делать каждый раз). Конечно, такие соображения не очень важны для относительно небольших файлов конфигурации.


Следующее реализует схему слияния для списков в Python с использованием объектов с тегом, flatten которые на лету рекурсируют в элементы, которые являются списками и помечены toflatten. Используя эти два тега, вы можете получить файл YAML:

l1: &x1 !toflatten
  - 1 
  - 2
l2: &x2
  - 3 
  - 4
m1: !flatten
  - *x1
  - *x2
  - [5, 6]
  - !toflatten [7, 8]

(использование последовательностей стилей потока и блока совершенно произвольно и не влияет на загруженный результат).

При итерации по элементам, которые являются значением для ключа, m1это "рекурсивно" превращается в последовательности, помеченныеtoflatten , но отображает другие списки (с псевдонимами или без них) как один элемент.

Один из возможных способов достижения этого с помощью кода Python:

import sys
from pathlib import Path
import ruamel.yaml

yaml = ruamel.yaml.YAML()


@yaml.register_class
class Flatten(list):
   yaml_tag = u'!flatten'
   def __init__(self, *args):
      self.items = args

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(*constructor.construct_sequence(node, deep=True))
       return x

   def __iter__(self):
       for item in self.items:
           if isinstance(item, ToFlatten):
               for nested_item in item:
                   yield nested_item
           else:
               yield item


@yaml.register_class
class ToFlatten(list):
   yaml_tag = u'!toflatten'

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(constructor.construct_sequence(node, deep=True))
       return x



data = yaml.load(Path('input.yaml'))
for item in data['m1']:
    print(item)

который выводит:

1
2
[3, 4]
[5, 6]
7
8

Как видите, вы можете видеть, что в последовательности, которая требует выравнивания, вы можете использовать псевдоним для помеченной последовательности или вы можете использовать помеченную последовательность. YAML не позволяет делать:

- !flatten *x2

, т. е. пометить закрепленную последовательность, так как это существенно превратит ее в другую структуру данных.

Использование явных тегов - это IMO лучше, чем какая-то магия, как с ключами слияния YAML <<. Если ничего другого, вам теперь придется пройти через обручи, если у вас есть файл YAML с сопоставлением, у которого есть ключ, <<который вы не хотите действовать как ключ слияния, например, когда вы делаете сопоставление операторов C с их описаниями на английском (или другом естественном языке).

Антон
источник
10

Если вам нужно объединить только один элемент в список, вы можете сделать

fruit:
  - &banana
    name: banana
    colour: yellow

food:
  - *banana
  - name: carrot
    colour: orange

что дает

fruit:
  - name: banana
    colour: yellow

food:
  - name: banana
    colour: yellow
  - name: carrot
    colour: orange
Тамлин
источник
-4

Вы можете объединить сопоставления, а затем преобразовать их ключи в список при следующих условиях:

  • если вы используете шаблоны jinja2 и
  • если порядок товара не важен
some_stuff: &some_stuff
 a:
 b:
 c:

combined_stuff:
  <<: *some_stuff
  d:
  e:
  f:

{{ combined_stuff | list }}
sm4rk0
источник
Что не так с этим ответом? Я не возражаю против отрицательных голосов, если они будут аргументированы. Я сохраню ответ для людей, которые могут им воспользоваться.
sm4rk0
3
Вероятно, потому что этот ответ основан на шаблоне jinja2, когда вопрос просит сделать это в yml. jinja2 требует среды Python, что контрпродуктивно, если OP пытается СУХОЙ. Кроме того, многие инструменты CI / CD не поддерживают создание шаблона.
Хорхе Лейтао
Спасибо @JorgeLeitao. В этом есть смысл. Я изучил YAML и Jinja2 вместе, разрабатывая сценарии и шаблоны Ansible, и не могу думать об одном без другого
sm4rk0