YAML dizileri nasıl birleştirilir?


113

YAML'de dizileri birleştirmek ve Ruby ile yüklemek istiyorum -

some_stuff: &some_stuff
 - a
 - b
 - c

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

Birleşik dizinin şu şekilde olmasını istiyorum [a,b,c,d,e,f]

Hatayı alıyorum: bir blok eşlemesini ayrıştırırken beklenen anahtarı bulamadım

YAML'de dizileri nasıl birleştiririm?


6
Bunu neden ayrıştırdığın dil yerine YAML'de yapmak istiyorsun?
Patrick Collins

7
çok büyük bir yaml dosyasında çoğaltmayı kurutmak için
lfender6445

4
Bu çok kötü bir uygulamadır. Yaml'leri ayrı ayrı okumalı, dizileri Ruby'de bir araya getirmeli ve ardından yaml'e geri yazmalısınız.
sawa

76
Nasıl kuru olmaya çalışmak kötü bir uygulamadır?
krak3n

13
@PatrickCollins Bu soruyu .gitlab-ci.yml dosyamda yinelemeyi azaltmaya çalışırken buldum ve maalesef GitLab CI'nın kullandığı ayrıştırıcı üzerinde hiçbir kontrolüm yok :(
rink.attendant. 6

Yanıtlar:


42

Amaç, bir dizi kabuk komutunu çalıştırmaksa, bunu aşağıdaki şekilde başarabilirsiniz:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Bu şuna eşdeğerdir:

some_stuff: "a\nb\nc"

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

Bunu gitlab-ci.yml(soruya @ rink.attendant.6 yorumumu yanıtlamak için) kullanıyorum.


requirements.txtGitlab'dan özel depolara sahip olmayı desteklemek için kullandığımız çalışma örneği :

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

nerede requirements_test.txtörneğin içeriyor

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


3
Zekice. Şimdi Bitbucket ardışık düzenimizde kullanıyorum. Teşekkürler
Dariop

* Burada son çizgi gerekli değildir, sadece sondaki boru yeterlidir. * Bu daha düşük bir çözümdür çünkü iş çok uzun bir çok satırlı ifadede başarısız olduğunda hangi komutun başarısız olduğu net değildir.
Mina Luke

1
@MinaLuke, neye kıyasla daha aşağı? Mevcut cevapların hiçbiri sadece yaml kullanarak iki maddeyi birleştirmek için bir yol sunmuyor ... Üstelik, soruda OP'nin bunu CI / CD'de kullanmak istediğini belirten hiçbir şey yok. Son olarak, bu CI / CD'de kullanıldığında, günlük kaydı, yaml bildirimine değil, yalnızca kullanılan belirli CI / CD'ye bağlıdır. Öyleyse, eğer bir şey varsa, bahsettiğiniz CI / CD, kötü bir iş çıkarandır. Bu cevaptaki yaml geçerlidir ve OP'nin problemini çözer.
Jorge Leitao

@JorgeLeitao Sanırım bunu Kuralları birleştirmek için kullanıyorsunuz. Çalışan bir gitlabci örneği verebilir misiniz? Çözümünüze göre bir şey denedim, ancak her zaman bir doğrulama hatası alıyorum.
Niels

1
benim için çalışmıyor. ile - Listeyi bir liste öğesinin içine eklemeye çalışmak gibi bir hata alıyorum. boruyu nasıl kullanacağımı bilmiyorum. bu ne içindi? @Dariop bunu BB Boru Hatlarında kullanmayı nasıl başardı?
Victor Ferreira

26

Güncelleme: 2019-07-01 14:06:12

  • Not : Bu soruya verilen başka bir cevap , alternatif yaklaşımlarla ilgili bir güncellemeyle büyük ölçüde düzenlendi .
    • Bu güncellenmiş cevap, bu cevaptaki geçici çözüme bir alternatiften bahsediyor. Aşağıdaki Ayrıca bkz bölümüne eklenmiştir .

Bağlam

Bu gönderi aşağıdaki bağlamı varsaymaktadır:

  • python 2.7
  • python YAML ayrıştırıcı

Sorun

lfender6445, bir YAML dosyası içinde iki veya daha fazla listeyi birleştirmek ve bu birleştirilmiş listelerin ayrıştırıldığında tek bir liste olarak görünmesini ister.

Çözüm (Geçici Çözüm)

Bu, basitçe, istenen listelerin eşlemelerin alt öğeleri olarak göründüğü eşlemelere YAML çapalarının atanmasıyla elde edilebilir. Bununla birlikte, bununla ilgili uyarılar vardır (bkz. Aşağıdaki "Tuzaklar").

Aşağıdaki örnekte list_one, list_two, list_three, uygun olduğunda bu eşlemelere başvuran üç eşlememiz ( ) ve üç çapa ve takma adımız vardır.

YAML dosyası programa yüklendiğinde, istediğimiz listeyi alırız, ancak yüklemeden sonra küçük bir değişiklik gerektirebilir (aşağıdaki tuzaklara bakın).

Misal

Orijinal YAML dosyası

  list_one: & id001
   - bir
   - b
   - c

  list_two: & id002
   - e
   - f
   - g

  list_three: & id003
   - h
   - ben
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

YAML.safe_load sonrası sonuç

## list_combined
  [
    [
      "a",
      "b",
      "c"
    ],
    [
      "e",
      "f",
      "g"
    ],
    [
      "h",
      "ben",
      "j"
    ]
  ]

Tuzaklar

Sonuç

Bu yaklaşım, YAML'nin takma adı ve çapa özelliği kullanılarak birleştirilmiş listelerin oluşturulmasına izin verir.

Çıktı sonucu iç içe geçmiş bir liste listesi olsa da, bu flattenyöntem kullanılarak kolayca dönüştürülebilir .

Ayrıca bakınız

@Anthon tarafından güncellenmiş alternatif yaklaşım

flattenYöntemin örnekleri


21

Bu işe yaramayacak:

  1. birleştirme yalnızca eşlemeler için YAML spesifikasyonları tarafından desteklenir, diziler için desteklenmez

  2. Bir birleştirme anahtarına ve << ardından anahtar / değer ayırıcısına :ve referans olan bir değere sahip olarak şeyleri tamamen karıştırıyorsunuz ve ardından aynı girinti düzeyinde bir listeyle devam ediyorsunuz

Bu doğru YAML değil:

combine_stuff:
  x: 1
  - a
  - b

Dolayısıyla, örnek söz diziminiz bir YAML uzantısı önerisi olarak bile mantıklı olmayacaktır.

Birden çok diziyi birleştirmek gibi bir şey yapmak istiyorsanız, aşağıdaki gibi bir sözdizimi düşünebilirsiniz:

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

burada s1, s2, s3yeni bir dizi halinde birleştirme istiyor ve sahip olduğu dizileri (gösterilmemiştir) ile çapa olan d, eve f buna ekli. Ancak YAML önce bu tür yapıları derinlemesine çözüyor, bu nedenle birleştirme anahtarının işlenmesi sırasında gerçek bir bağlam mevcut değil. İşlenen değeri (bağlantılı sıra) ekleyebileceğiniz bir dizi / liste mevcut değildir.

Yaklaşımı @dreftymac tarafından önerildiği gibi alabilirsiniz, ancak bu büyük bir dezavantaja sahiptir, bir şekilde hangi iç içe dizilerin düzleştirileceğini bilmeniz gerekir (yani, yüklenen veri yapısının kökünden ana diziye giden "yolu" bilmek), ya da iç içe dizileri / listeleri aramak için yüklü veri yapısını yinelemeli olarak yürüdüğünüz ve ayrım gözetmeksizin hepsini düzleştirdiğiniz.

Daha iyi bir çözüm IMO, sizin için düzleştirmeyi yapan veri yapılarını yüklemek için etiketleri kullanmak olacaktır. Bu, neyin düzleştirilmesi gerektiğini ve neyin düzleştirilmeyeceğini açıkça belirtmenize olanak tanır ve bu düzleştirmenin yükleme sırasında mı yoksa erişim sırasında mı yapılacağı konusunda size tam kontrol sağlar. Hangisini seçeceğiniz, zaman ve depolama alanında uygulama kolaylığı ve verimlilik meselesidir. Bu, birleştirme anahtarı özelliğini uygulamak için yapılması gereken aynı ödünleşmedir ve her zaman en iyi olan tek bir çözüm yoktur.

Örneğin, ruamel.yamlkütüphanem güvenli yükleyicisini kullanırken yükleme sırasında kaba kuvvet birleştirme kurallarını kullanıyor, bu da normal Python sözlükleri olan birleştirilmiş sözlüklerle sonuçlanıyor. Bu birleştirme önceden yapılmalıdır ve verileri çoğaltır (alan verimsizdir) ancak değer aramasında hızlıdır. Gidiş-dönüş yükleyiciyi kullanırken, birleştirmeleri birleştirmeden atabilmek istersiniz, bu yüzden ayrı tutulmaları gerekir. Gidiş-dönüş yüklemenin bir sonucu olarak yüklenen veri yapısı gibi dikte, alan açısından verimli ancak erişimde daha yavaştır, çünkü birleşmelerde diktede bulunmayan bir anahtarı denemek ve aramak zorundadır (ve bu önbelleğe alınmaz, bu nedenle her seferinde yapılması gerekir). Elbette bu tür düşünceler, nispeten küçük yapılandırma dosyaları için çok önemli değildir.


Aşağıdakiler flatten , python'daki listeler için, anında listelenmiş ve etiketlenmiş öğelere dönüşen etiketli nesneleri kullanarak birleştirme benzeri bir şema uygular toflatten. Bu iki etiketi kullanarak YAML dosyasına sahip olabilirsiniz:

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

(akışa karşı blok tarzı dizilerin kullanımı tamamen keyfidir ve yüklenen sonuç üzerinde hiçbir etkisi yoktur).

Anahtar için değer olan öğeler üzerinde yineleme yapıldığında, m1bu toflatten, ile etiketlenmiş dizilere "yinelenir" , ancak diğer listeleri (takma ad olan veya olmayan) tek bir öğe olarak görüntüler.

Python kodu ile bunu başarmanın olası bir yolu şudur:

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)

hangi çıktılar:

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

Gördüğünüz gibi, düzleştirilmesi gereken dizide, etiketli bir dizinin takma adını kullanabilir veya etiketli bir sıra kullanabilirsiniz. YAML şunları yapmanıza izin vermiyor:

- !flatten *x2

yani bağlantılı bir sekansı etiketleyin, çünkü bu onu esasen farklı bir veri yapısına dönüştürür.

Açık etiketleri kullanmak IMO, YAML birleştirme anahtarlarında olduğu gibi bazı sihirlere sahip olmaktan daha iyidir <<. Başka bir şey yoksa <<, bir birleştirme anahtarı gibi davranmak istemediğiniz bir anahtarı olan bir eşlemeye sahip bir YAML dosyanız varsa, örneğin, açıklamalarına C operatörlerinin bir eşlemesini yaptığınızda, çemberlerden geçmek zorunda kalırsınız. İngilizce (veya başka bir doğal dilde).


10

Yalnızca bir öğeyi bir listede birleştirmeniz gerekiyorsa şunları yapabilirsiniz:

fruit:
  - &banana
    name: banana
    colour: yellow

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

hangi verim

fruit:
  - name: banana
    colour: yellow

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

-4

Aşağıdaki koşullar altında eşlemeleri birleştirip anahtarlarını bir listeye dönüştürebilirsiniz:

  • jinja2 templating kullanıyorsanız ve
  • öğe sırası önemli değilse
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}

Bu cevabın nesi var? Tartışılırsa, olumsuz oyları önemsemem. Cevabı, onu kullanabilecek insanlar için saklayacağım.
sm4rk0

3
Muhtemelen soru yml olarak yapılmasını istediğinde, bu yanıt jinja2 şablonlamasına dayandığı için. jinja2 bir Python ortamı gerektirir ve bu, OP DRY olmaya çalışıyorsa ters etki yapar. Ayrıca, birçok CI / CD aracı şablon oluşturma adımını kabul etmez.
Jorge Leitao

Teşekkürler @JorgeLeitao. Mantıklı. Ansible oyun kitapları ve şablonları geliştirirken YAML ve Jinja2'yi birlikte öğrendim ve biri olmadan birini düşünemiyorum
sm4rk0
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.