Wie füge ich YAML-Arrays zusammen?

110

Ich möchte Arrays in YAML zusammenführen und über Ruby laden -

some_stuff: &some_stuff
 - a
 - b
 - c

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

Ich möchte das kombinierte Array als haben [a,b,c,d,e,f]

Ich erhalte die Fehlermeldung: Beim Parsen einer Blockzuordnung wurde der erwartete Schlüssel nicht gefunden

Wie füge ich Arrays in YAML zusammen?

lfender6445
quelle
6
Warum möchten Sie dies in YAML tun und nicht in der Sprache, mit der Sie es analysieren?
Patrick Collins
7
Duplizierung in einer sehr großen Yaml-Datei zu
trocknen
4
Das ist eine sehr schlechte Praxis. Sie sollten yamls separat lesen, die Arrays in Ruby zusammenfügen und dann zurück in yaml schreiben.
Sawa
73
Wie versucht man trocken zu sein?
krak3n
13
@PatrickCollins Ich habe diese Frage beim Versuch gefunden, die Duplizierung in meiner .gitlab-ci.yml- Datei zu reduzieren, und leider habe ich keine Kontrolle über den Parser, den GitLab CI verwendet :(
rink.attendant.6

Antworten:

40

Wenn das Ziel darin besteht, eine Folge von Shell-Befehlen auszuführen, können Sie dies möglicherweise wie folgt erreichen:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Dies entspricht:

some_stuff: "a\nb\nc"

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

Ich habe dies auf meinem gitlab-ci.yml(um @ rink.attendant.6 Kommentar zu der Frage zu beantworten) verwendet.


Arbeitsbeispiel, das wir verwenden, um requirements.txtprivate Repos von gitlab zu unterstützen:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://[email protected]"
- 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...

wo requirements_test.txtenthält zB

-e git+ssh://[email protected]/example/[email protected]#egg=example

Jorge Leitao
quelle
3
Klug. Ich verwende es jetzt in unserer Bitbucket-Pipeline. Danke
Dariop
* Der hintere Strich ist hier nicht erforderlich, nur das Rohr am Ende reicht aus. * Dies ist eine minderwertige Lösung, da nicht klar ist, welcher Befehl fehlgeschlagen ist, wenn der Job bei einer sehr langen mehrzeiligen Anweisung fehlschlägt.
Mina Luke
1
@MinaLuke, minderwertig im Vergleich zu was? Keine der aktuellen Antworten bietet eine Möglichkeit, zwei Elemente nur mit Yaml zusammenzuführen ... Darüber hinaus enthält die Frage nichts, was besagt, dass das OP dies in CI / CD verwenden möchte. Wenn dies in CI / CD verwendet wird, hängt die Protokollierung nur von dem jeweiligen verwendeten CI / CD ab, nicht von der yaml-Deklaration. Wenn überhaupt, ist die CI / CD, auf die Sie sich beziehen, diejenige, die einen schlechten Job macht. Das Yaml in dieser Antwort ist gültig und löst das Problem von OP.
Jorge Leitao
@JorgeLeitao Ich denke, Sie verwenden es, um Regeln zu kombinieren. Können Sie ein funktionierendes Gitlabci-Beispiel liefern? Ich habe etwas versucht, das auf Ihrer Lösung basiert, erhalte aber immer einen Validierungsfehler.
Niels
@niels, ich habe ein Beispiel mit einem funktionierenden Gitlabci-Beispiel hinzugefügt. Beachten Sie, dass einige IDEs dieses Yaml als ungültig markieren, obwohl dies nicht der Fall ist.
Jorge Leitao
26

Update: 2019-07-01 14:06:12

  • Hinweis : Eine weitere Antwort auf diese Frage wurde im Wesentlichen mit einem Update zu alternativen Ansätzen bearbeitet .
    • In dieser aktualisierten Antwort wird eine Alternative zur Problemumgehung in dieser Antwort erwähnt. Es wurde dem Abschnitt Siehe auch unten hinzugefügt .

Kontext

Dieser Beitrag nimmt den folgenden Kontext an:

  • Python 2.7
  • Python YAML Parser

Problem

lfender6445 möchte zwei oder mehr Listen in einer YAML-Datei zusammenführen und diese zusammengeführten Listen beim Analysieren als eine einzige Liste anzeigen lassen.

Lösung (Problemumgehung)

Dies kann einfach durch Zuweisen von YAML-Ankern zu Zuordnungen erreicht werden, wobei die gewünschten Listen als untergeordnete Elemente der Zuordnungen angezeigt werden. Dies hat jedoch einige Einschränkungen (siehe "Fallstricke" weiter unten).

Im folgenden Beispiel haben wir drei Zuordnungen ( list_one, list_two, list_three) und drei Anker und Aliase, die gegebenenfalls auf diese Zuordnungen verweisen.

Wenn die YAML-Datei in das Programm geladen wird, erhalten wir die gewünschte Liste, die jedoch nach dem Laden möglicherweise geringfügig geändert werden muss (siehe Fallstricke unten).

Beispiel

Original YAML-Datei

  list_one: & id001
   - ein
   - b
   - c

  list_two: & id002
   - e
   - f
   - g

  list_three: & id003
   - h
   - ich
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Ergebnis nach YAML.safe_load

## list_combined
  [
    [
      "ein",
      "b",
      "c"
    ],
    [
      "e",
      "f",
      "G"
    ],
    [
      "h",
      "ich",
      "j"
    ]]
  ]]

Tücken

  • Dieser Ansatz erzeugt eine verschachtelte Liste von Listen, die möglicherweise nicht die exakt gewünschte Ausgabe sind, die jedoch mithilfe der Abflachungsmethode nachbearbeitet werden kann
  • Die üblichen Einschränkungen für YAML-Anker und Aliase gelten für die Eindeutigkeit und die Reihenfolge der Deklaration

Fazit

Dieser Ansatz ermöglicht die Erstellung zusammengeführter Listen mithilfe der Alias- und Ankerfunktion von YAML.

Obwohl das Ausgabeergebnis eine verschachtelte Liste von Listen ist, kann dies mit der flattenMethode leicht transformiert werden .

Siehe auch

Aktualisierter alternativer Ansatz von @Anthon

Beispiele für die flattenMethode

dreftymac
quelle
20

Das wird nicht funktionieren:

  1. Das Zusammenführen wird nur von den YAML-Spezifikationen für Zuordnungen und nicht für Sequenzen unterstützt

  2. Sie mischen die Dinge vollständig, indem Sie einen Zusammenführungsschlüssel << gefolgt vom Schlüssel- / Werttrennzeichen :und einem Wert als Referenz verwenden und dann mit einer Liste auf derselben Einrückungsstufe fortfahren

Dies ist nicht korrekt YAML:

combine_stuff:
  x: 1
  - a
  - b

Ihre Beispielsyntax wäre also als YAML-Erweiterungsvorschlag nicht einmal sinnvoll.

Wenn Sie beispielsweise mehrere Arrays zusammenführen möchten, sollten Sie eine Syntax wie die folgende in Betracht ziehen:

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

wo s1, s2, s3sind Anker auf Sequenzen (nicht dargestellt) , dass Sie in eine neue Sequenz zu fusionieren wollen und haben dann die d, eund f der angehängt. YAML löst diese Art von Strukturtiefe jedoch zuerst auf, sodass während der Verarbeitung des Zusammenführungsschlüssels kein realer Kontext verfügbar ist. Es steht Ihnen kein Array / keine Liste zur Verfügung, an die Sie den verarbeiteten Wert (die verankerte Sequenz) anhängen könnten.

Sie können den von @dreftymac vorgeschlagenen Ansatz wählen, dies hat jedoch den großen Nachteil, dass Sie irgendwie wissen müssen, welche verschachtelten Sequenzen abgeflacht werden sollen (dh indem Sie den "Pfad" von der Wurzel der geladenen Datenstruktur zur übergeordneten Sequenz kennen). oder dass Sie die geladene Datenstruktur rekursiv durchsuchen, nach verschachtelten Arrays / Listen suchen und diese wahllos reduzieren.

Eine bessere Lösung IMO wäre die Verwendung von Tags zum Laden von Datenstrukturen, die die Abflachung für Sie übernehmen. Auf diese Weise können Sie klar angeben, was abgeflacht werden muss und was nicht, und Sie haben die volle Kontrolle darüber, ob diese Abflachung während des Ladens oder während des Zugriffs erfolgt. Welche Sie wählen sollten, hängt von der einfachen Implementierung und der Effizienz in Bezug auf Zeit und Speicherplatz ab. Dies ist der gleiche Kompromiss , dass der Bedarf für die Umsetzung des gemacht wird merge Schlüsselmerkmal , und es gibt keine einheitliche Lösung , die immer die beste ist.

Beispielsweise verwendet meine ruamel.yamlBibliothek beim Laden die Brute-Force-Merge-Dicts, wenn sie ihren Safe-Loader verwendet. Dies führt zu zusammengeführten Wörterbüchern, die normale Python-Dicts sind. Diese Zusammenführung muss im Voraus erfolgen und dupliziert Daten (Speicherplatz ineffizient), ist jedoch bei der Wertsuche schnell. Wenn Sie den Roundtrip-Loader verwenden, möchten Sie in der Lage sein, die Zusammenführungen nicht zusammengeführt zu speichern, sodass sie getrennt aufbewahrt werden müssen. Das Diktat wie eine Datenstruktur, die als Ergebnis des Round-Trip-Ladens geladen wird, ist platzsparend, aber langsamer im Zugriff, da versucht werden muss, einen Schlüssel zu suchen, der im Diktat selbst in den Zusammenführungen nicht enthalten ist (und dieser wird nicht zwischengespeichert, also ist es so) muss jedes Mal gemacht werden). Natürlich sind solche Überlegungen für relativ kleine Konfigurationsdateien nicht sehr wichtig.


Im Folgenden wird ein zusammenführungsähnliches Schema für Listen in Python implementiert, bei dem Objekte mit einem Tag verwendet werden, flatten das im laufenden Betrieb in Elemente umgewandelt wird, die Listen und Tags sind toflatten. Mit diesen beiden Tags können Sie eine YAML-Datei haben:

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

(Die Verwendung von Flow- und Block-Style-Sequenzen ist völlig willkürlich und hat keinen Einfluss auf das geladene Ergebnis.)

Beim Durchlaufen der Elemente, die den Wert für den Schlüssel darstellen, m1"rekursiert" dies in die mit Tags gekennzeichneten Sequenzen toflatten, zeigt jedoch andere Listen (mit oder ohne Alias) als einzelnes Element an.

Ein möglicher Weg, dies mit Python-Code zu erreichen, ist:

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)

welche Ausgänge:

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

Wie Sie sehen können, können Sie in der Sequenz, die abgeflacht werden muss, entweder einen Alias ​​für eine markierte Sequenz oder eine markierte Sequenz verwenden. YAML erlaubt dir nicht:

- !flatten *x2

, dh eine verankerte Sequenz markieren, da dies im Wesentlichen zu einer anderen Datenstruktur führen würde.

Die Verwendung expliziter Tags ist IMO besser als etwas Magie wie bei YAML-Zusammenführungsschlüsseln <<. Wenn Sie eine YAML-Datei mit einer Zuordnung haben, die einen Schlüssel enthält <<, den Sie nicht wie einen Zusammenführungsschlüssel verhalten möchten, müssen Sie jetzt die Rahmen durchlaufen , z. B. wenn Sie eine Zuordnung von C-Operatoren zu ihren Beschreibungen vornehmen in Englisch (oder einer anderen natürlichen Sprache).

Anthon
quelle
9

Wenn Sie nur ein Element in einer Liste zusammenführen müssen, können Sie dies tun

fruit:
  - &banana
    name: banana
    colour: yellow

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

was ergibt

fruit:
  - name: banana
    colour: yellow

food:
  - name: banana
    colour: yellow
  - name: carrot
    colour: orange
Tamlyn
quelle
-4

Sie können Zuordnungen zusammenführen und ihre Schlüssel dann unter folgenden Bedingungen in eine Liste konvertieren:

  • wenn Sie jinja2 templating verwenden und
  • wenn die Artikelbestellung nicht wichtig ist
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}
sm4rk0
quelle
Was ist los mit dieser Antwort? Ich habe nichts gegen Abstimmungen, wenn sie argumentiert werden. Ich werde die Antwort für Leute behalten, die davon Gebrauch machen können.
sm4rk0
3
Wahrscheinlich, weil diese Antwort auf jinja2-Vorlagen basiert, wenn die Frage danach fragt, dies in yml zu tun. jinja2 erfordert eine Python-Umgebung, die kontraproduktiv ist, wenn das OP versucht zu trocknen. Außerdem akzeptieren viele CI / CD-Tools keinen Vorlagenschritt.
Jorge Leitao
Danke @JorgeLeitao. Das macht Sinn. Ich habe YAML und Jinja2 zusammen gelernt, während ich Ansible-Playbooks und -Vorlagen entwickelt habe, und kann nicht ohne einander denken
sm4rk0