YAML配列をマージする方法は?


112

YAMLで配列をマージし、ruby経由でロードしたい-

some_stuff: &some_stuff
 - a
 - b
 - c

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

私は結合された配列を次のようにしたいのですが [a,b,c,d,e,f]

エラーを受け取ります:ブロックマッピングの解析中に予期したキーが見つかりませんでした

YAMLで配列をマージするにはどうすればよいですか?


6
なぜこれを解析する言語ではなくYAMLで実行したいのですか?
Patrick Collins

7
非常に大きなyamlファイルの重複をなくすため
lfender6445

4
これは非常に悪い習慣です。yamlを個別に読み取り、Rubyで配列をまとめ、それをyamlに書き戻す必要があります。
澤沢2014

74
乾いた悪い習慣になろうとしていますか?
krak3n

13
@PatrickCollins .gitlab-ci.ymlファイルの重複を減らすためにこの質問を見つけましたが、残念ながら、GitLab CIが使用するパーサーを制御できません:(
rink.attendant.6

回答:


40

シェルコマンドのシーケンスを実行することが目的である場合は、次のようにしてこれを実現できる場合があります。

# 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.txtgitlabからのプライベートリポジトリのサポートに使用する作業例:

.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

*トレーリングダッシュはここでは必要ありません、最後のパイプだけで十分です。*ジョブが非常に長い複数行のステートメントで失敗した場合、どのコマンドが失敗したかが明確でないため、これは劣ったソリューションです。
Mina Luke、

1
@MinaLuke、何と比較して劣っていますか?現在の回答はいずれも、yamlだけを使用して2つのアイテムをマージする方法を提供していません...さらに、OPがCI / CDでこれを使用することを望んでいることを示す質問には何もありません。最後に、これがCI / CDで使用される場合、ロギングはyaml宣言ではなく、使用される特定のCI / CDにのみ依存します。したがって、どちらかといえば、あなたが参照しているCI / CDは、悪い仕事をしているものです。この回答のyamlは有効であり、OPの問題を解決します。
ホルヘレイタオ、

@JorgeLeitaoルールの結合に使用しているようです。動作するgitlabciの例を提供できますか?私はあなたの解決策に基づいて何かを試しましたが、常に検証エラーが発生します。
niels

@niels、私はgitlabciの実際の例とともに例を追加しました。一部のIDEはこのyamlを無効としてマークしますが、無効ではないことに注意してください。
ホルヘレイタオ

26

更新:2019-07-01 14:06:12

  • :この質問に対する別の回答は、代替アプローチの更新により大幅に編集されました。
    • その更新された回答は、この回答の回避策の代替案について言及しています。以下の「関連項目」セクションに追加されています。

環境

この投稿は、次のコンテキストを前提としています。

  • Python 2.7
  • Python YAMLパーサー

問題

lfender6445は、YAMLファイル内の2つ以上のリストをマージし、それらのマージされたリストを解析時に1つの特異なリストとして表示させたいと考えています。

解決策(回避策)

これは、YAMLアンカーをマッピングに割り当てるだけで取得できます。この場合、必要なリストがマッピングの子要素として表示されます。ただし、これには注意が必要です(下記の「落とし穴」を参照)。

以下の例では、3つのマッピング(list_one, list_two, list_three)と、これらのマッピングを適切に参照する3つのアンカーとエイリアスがあります。

YAMLファイルがプログラムに読み込まれると、必要なリストが取得されますが、読み込み後に少し変更が必要になる場合があります(以下の落とし穴を参照)。

元のYAMLファイル

  list_one:&id001
   -a
   -b
   -c

  list_two:&id002
   -e
   -f
   -g

  list_three:&id003
   -時間
   - 私
   -j

  list_combined:
      -* id001
      -* id002
      -* id003

YAML.safe_load後の結果

## list_combined
  [
    [
      "a"、
      "b"、
      「c」
    ]、
    [
      "e"、
      "f"、
      「g」
    ]、
    [
      "h"、
      "私"、
      「j」
    ]
  ]

落とし穴

  • このアプローチはリストのネストされたリストを生成しますが、これは正確な望ましい出力ではない可能性がありますが、これはflattenメソッドを使用して後処理できます
  • YAMLアンカーとエイリアスへの通常の注意事項は一意と宣言順に適用されます

結論

このアプローチでは、YAMLのエイリアスとアンカー機能を使用して、マージされたリストを作成できます。

出力結果はリストのネストされたリストですが、これはflattenメソッドを使用して簡単に変換できます。

こちらもご覧ください

@Anthonによる代替アプローチの更新

flatten方法の例


21

これはうまくいきません:

  1. マージはYAML仕様でのみサポートされており、シーケンスではサポートされていません

  2. あなたは<< マージキー:とそれに続くキー/値のセパレータと参照である値を持ち、同じインデントレベルでリストを続けることで完全に混合しています

これは正しいYAMLではありません。

combine_stuff:
  x: 1
  - a
  - b

したがって、あなたの構文例はYAML拡張の提案としては意味がありません。

複数の配列をマージするようなことをしたい場合は、次のような構文を検討する必要があります。

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

どこにs1s2s3あなたが新しい順に併合してから持ちたいというシーケンス上のアンカー(図示せず)されdeそしてf それに追加。しかし、YAMLはこれらの種類の構造の深さを最初に解決するため、マージキーの処理中に利用できる実際のコンテキストはありません。処理された値(アンカーされたシーケンス)をア​​タッチできる配列/リストはありません。

@dreftymacで提案されているアプローチを取ることができますが、これには、どのネストされたシーケンスをフラット化するかを知る必要があるという大きな欠点があります(つまり、ロードされたデータ構造のルートから親シーケンスへの「パス」を知ることによって)。または、ネストされた配列/リストを検索してロードされたデータ構造を再帰的にウォークし、それらすべてを無差別にフラット化します。

IMOのより良い解決策は、タグを使用してデータ構造をロードし、フラット化を行うことです。これにより、フラット化が必要なものとそうでないものを明確に示すことができ、このフラット化がロード中に行われるか、アクセス中に行われるかを完全に制御できます。どちらを選択するかは、実装の容易さと時間とストレージスペースの効率の問題です。これは、同じトレードオフのニーズが実現するためになされるべきことであるマージキーの機能を、常に最善である単一の解決策がありません。

たとえば、私のruamel.yamlライブラリは、セーフローダーを使用している場合、ロード時にブルートフォースのマージディクテーションを使用します。これにより、通常のPythonディクトである辞書がマージされます。このマージは事前に行う必要があり、データを複製しますが(スペース効率が悪い)、値の検索は高速です。往復ローダーを使用する場合、マージをマージせずにダンプできるようにしたいので、それらを別々に保つ必要があります。往復読み込みの結果として読み込まれるデータ構造のようなdictは、スペース効率は良いですが、マージでdict自体にないキーを検索する必要があるため、アクセスが遅くなります(これはキャッシュされないため、毎回行う必要があります)。もちろん、このような考慮事項は、比較的小さな構成ファイルではあまり重要ではありません。


以下flatten は、リストとタグ付けされた項目にオンザフライで再帰するタグ付きオブジェクトを使用して、Pythonのリストのマージのようなスキームを実装しますtoflatten。これらの2つのタグを使用して、YAMLファイルを作成できます。

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

(フローvsブロックスタイルシーケンスの使用は完全に任意であり、ロードされた結果には影響しません)。

keyの値であるアイテムを反復する場合、これはでm1タグ付けされたシーケンスに「再帰」しtoflattenますが、他のリスト(エイリアスの有無にかかわらず)を単一のアイテムとして表示します。

これを実現するためのPythonコードの可能な方法の1つは次のとおりです。

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

つまり、アンカーされたシーケンスにタグを付けます。これにより、本質的に異なるデータ構造になります。

明示的なタグを使用することは、YAMLマージキーのように魔法をかけるよりもIMOの方が優れています<<。他に何もない場合、マージキーの<<ように動作させたくないキーを持つマッピングを持つYAMLファイルがある場合は、フープを通過する必要 があります。たとえば、C演算子をその説明にマッピングする場合英語(または他の自然言語)。


9

1つのアイテムをリストにマージするだけでよい場合は、

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

3
この回答はjinja2テンプレートに依存している可能性が高いため、質問でymlで実行するよう求められます。jinja2にはPython環境が必要です。これは、OPがDRYを実行しようとすると逆効果になります。また、多くのCI / CDツールはテンプレートのステップを受け入れません。
ホルヘレイタオ、

@JorgeLeitaoに感謝します。それは理にかなっている。YAMLとJinja2を一緒に学び、Ansibleのプレイブックとテンプレートを開発しました。他の
アプリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.