วิธีผสานอาร์เรย์ YAML


113

ฉันต้องการรวมอาร์เรย์ใน 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
นี่เป็นการปฏิบัติที่แย่มาก คุณควรอ่านมันแกวแยกจากกันใส่อาร์เรย์เข้าด้วยกันใน Ruby จากนั้นเขียนกลับไปที่ yaml
sawa

76
พยายามที่จะปฏิบัติไม่ดีแห้งอย่างไร?
krak3n

13
@PatrickCollins ฉันพบว่าคำถามนี้พยายามลดความซ้ำซ้อนในไฟล์. gitlab-ci.ymlของฉันและน่าเสียดายที่ฉันไม่สามารถควบคุมตัวแยกวิเคราะห์ที่ GitLab CI ใช้ :(
rink.attendant

คำตอบ:


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มี repos ส่วนตัวจาก 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

* ที่นี่ไม่จำเป็นต้องใช้เส้นประต่อท้ายมีเพียงท่อที่ปลายก็เพียงพอแล้ว * นี่เป็นโซลูชันที่ด้อยกว่าเนื่องจากเมื่องานล้มเหลวในคำสั่งหลายบรรทัดที่ยาวมากจึงไม่ชัดเจนว่าคำสั่งใดล้มเหลว
Mina Luke

1
@MinaLuke ด้อยกว่าเมื่อเทียบกับอะไร? ไม่มีคำตอบใดในปัจจุบันที่ให้วิธีการรวมสองรายการโดยใช้ yaml เท่านั้น ... ยิ่งไปกว่านั้นไม่มีคำถามใดที่ระบุว่า OP ต้องการใช้สิ่งนี้ใน CI / CD สุดท้ายเมื่อใช้ใน CI / CD การบันทึกจะขึ้นอยู่กับ CI / CD ที่ใช้เท่านั้นไม่ใช่ในการประกาศ yaml ดังนั้นหากมีสิ่งใด CI / CD ที่คุณอ้างถึงคือสิ่งที่ทำงานได้ไม่ดี ยำในคำตอบนี้ใช้ได้และแก้ปัญหาของ OP ได้
Jorge Leitao

@JorgeLeitao ฉันเดาว่าคุณใช้มันเพื่อรวมกฎ คุณสามารถให้ตัวอย่าง gitlabci ที่ใช้งานได้หรือไม่? ฉันลองทำตามวิธีแก้ปัญหาของคุณแล้ว แต่ได้รับข้อผิดพลาดในการตรวจสอบความถูกต้องเสมอ
niels

1
มันไม่ได้ผลสำหรับฉัน ด้วย - ฉันได้รับข้อผิดพลาดเช่นพยายามแทรกรายการเข้าไปในรายการ ฉันไม่รู้วิธีใช้ท่อ มีไว้เพื่ออะไร? @Dariop จัดการกับการใช้งานใน BB Pipelines อย่างไร?
Victor Ferreira

26

ปรับปรุง: 2019-07-01 14:06:12

  • หมายเหตุ : คำตอบอื่นสำหรับคำถามนี้ได้รับการแก้ไขอย่างมากโดยมีการอัปเดตเกี่ยวกับแนวทางอื่น
    • คำตอบที่อัปเดตนั้นกล่าวถึงทางเลือกในการแก้ปัญหาในคำตอบนี้ มีการเพิ่มเข้าไปในส่วนดูเพิ่มเติมด้านล่าง

บริบท

โพสต์นี้ถือว่าบริบทต่อไปนี้:

  • หลาม 2.7
  • python YAML parser

ปัญหา

lfender6445 ต้องการรวมสองรายการขึ้นไปภายในไฟล์ YAML และให้รายการที่รวมเหล่านั้นปรากฏเป็นรายการเอกพจน์เดียวเมื่อแยกวิเคราะห์

โซลูชัน (วิธีแก้ปัญหาชั่วคราว)

สิ่งนี้สามารถหาได้ง่ายๆโดยการกำหนดจุดยึด YAML ให้กับการแมปโดยรายการที่ต้องการจะปรากฏเป็นองค์ประกอบลูกของการแมป อย่างไรก็ตามมีข้อควรระวังสำหรับเรื่องนี้ (ดู "ข้อผิดพลาด" ด้านล่าง)

ในตัวอย่างด้านล่างเรามีการแมปสามรายการ ( list_one, list_two, list_three) และจุดยึดและนามแฝงสามรายการที่อ้างถึงการแมปเหล่านี้ตามความเหมาะสม

เมื่อไฟล์ YAML ถูกโหลดในโปรแกรมเราจะได้รับรายชื่อที่เราต้องการ แต่อาจต้องมีการแก้ไขเล็กน้อยหลังจากโหลด (ดูข้อผิดพลาดด้านล่าง)

ตัวอย่าง

ไฟล์ YAML ดั้งเดิม

  list_one: & id001
   - ก
   - ข
   - ค

  list_two: & id002
   - จ
   - ฉ
   - ก

  list_three: & id003
   - ชม
   - ผม
   - ญ

  list_combined:
      - * id001
      - * id002
      - * id003

ผลลัพธ์หลังจาก YAML.safe_load

## list_combined
  [
    [
      "a",
      "ข",
      "ค"
    ],
    [
      "e",
      "ฉ",
      "g"
    ],
    [
      "h",
      "ผม",
      "j"
    ]
  ]

หลุมพราง

  • วิธีนี้สร้างรายการที่ซ้อนกันซึ่งอาจไม่ใช่ผลลัพธ์ที่ต้องการ แต่สามารถประมวลผลภายหลังได้โดยใช้วิธีแบน
  • caveats ปกติในการเบรก 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

ที่s1, s2, s3มีเบรกลำดับ (ไม่แสดง) ที่คุณต้องการที่จะรวมกันเป็นลำดับใหม่แล้วมีd, eและf ต่อท้ายว่า แต่ YAML กำลังแก้ไขความลึกของโครงสร้างประเภทนี้ก่อนดังนั้นจึงไม่มีบริบทที่แท้จริงในระหว่างการประมวลผลคีย์การผสาน ไม่มีอาร์เรย์ / รายการสำหรับคุณที่คุณสามารถแนบค่าที่ประมวลผลแล้ว (ลำดับที่ยึด) กับ

คุณสามารถใช้แนวทางตามที่เสนอโดย @dreftymac แต่สิ่งนี้มีข้อเสียอย่างมากที่คุณจำเป็นต้องรู้ว่าลำดับที่ซ้อนกันใดที่จะทำให้แบน (กล่าวคือโดยการรู้ "เส้นทาง" จากรากของโครงสร้างข้อมูลที่โหลดไปยังลำดับพาเรนต์) หรือว่าคุณเดินตามโครงสร้างข้อมูลที่โหลดซ้ำเพื่อค้นหาอาร์เรย์ / รายการที่ซ้อนกันและทำให้แบนราบทั้งหมด

ทางออกที่ดีกว่า IMO คือการใช้แท็กเพื่อโหลดโครงสร้างข้อมูลที่ทำให้แบนสำหรับคุณ สิ่งนี้ช่วยให้สามารถระบุได้อย่างชัดเจนว่าต้องแบนอะไรและอะไรไม่ได้และช่วยให้คุณควบคุมได้อย่างเต็มที่ว่าการแบนนี้จะเกิดขึ้นระหว่างการโหลดหรือทำในระหว่างการเข้าถึง สิ่งที่ต้องเลือกคือเรื่องของการใช้งานที่ง่ายและมีประสิทธิภาพในเวลาและพื้นที่จัดเก็บ นี่เป็นการแลกเปลี่ยนแบบเดียวกับที่ต้องทำเพื่อใช้คุณลักษณะคีย์การผสานและไม่มีโซลูชันเดียวที่ดีที่สุดเสมอไป

เช่นruamel.yamlไลบรารีของฉันใช้ brute force merge-dict ในระหว่างการโหลดเมื่อใช้ safe-loader ซึ่งส่งผลให้พจนานุกรมรวมที่เป็น Python ตามปกติ การรวมนี้จะต้องดำเนินการล่วงหน้าและข้อมูลที่ซ้ำกัน (พื้นที่ไม่มีประสิทธิภาพ) แต่สามารถค้นหาค่าได้อย่างรวดเร็ว เมื่อใช้ตัวโหลดแบบไปกลับคุณต้องการที่จะสามารถถ่ายโอนข้อมูลการผสานที่ไม่ได้ผสานดังนั้นจึงต้องแยกกัน คำสั่งเช่นโครงสร้างข้อมูลที่โหลดอันเป็นผลมาจากการโหลดแบบไปกลับเป็นพื้นที่ที่มีประสิทธิภาพ แต่เข้าถึงได้ช้าลงเนื่องจากต้องลองและค้นหาคีย์ที่ไม่พบในตัวเองในการผสาน (และสิ่งนี้ไม่ได้ถูกแคชไว้ดังนั้น ต้องทำทุกครั้ง) แน่นอนว่าการพิจารณาดังกล่าวไม่สำคัญมากสำหรับไฟล์คอนฟิกูเรชันที่ค่อนข้างเล็ก


ต่อไปนี้การดำเนินการผสานเช่นโครงการสำหรับรายการในหลามใช้วัตถุที่มีแท็กflatten ซึ่ง on-the-fly การ recurses toflattenเข้าไปในรายการซึ่งเป็นรายการและติดแท็ก การใช้สองแท็กนี้คุณสามารถมีไฟล์ YAML:

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

(การใช้ลำดับสไตล์โฟลว์ VS บล็อกเป็นไปตามอำเภอใจและไม่มีผลต่อผลลัพธ์ที่โหลด)

เมื่อวนซ้ำรายการที่เป็นค่าสำหรับคีย์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 ที่มีการแมปที่มีคีย์ <<ที่คุณไม่ต้องการทำหน้าที่เหมือนคีย์ผสานเช่นเมื่อคุณทำการแมปตัวดำเนินการ 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 templating และ
  • ถ้ารายการสั่งซื้อไม่สำคัญ
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}

คำตอบนี้ผิดอะไร ฉันไม่รังเกียจที่จะลงคะแนนหากมีการโต้แย้ง ฉันจะเก็บคำตอบสำหรับผู้ที่สามารถใช้ประโยชน์ได้
sm4rk0

3
อาจเป็นเพราะคำตอบนี้อาศัยเทมเพลต jinja2 เมื่อคำถามขอให้ทำใน yml jinja2 ต้องการสภาพแวดล้อม Python ซึ่งต่อต้านการผลิตหาก OP พยายามทำให้แห้ง นอกจากนี้เครื่องมือ CI / CD จำนวนมากไม่ยอมรับขั้นตอนเทมเพลต
Jorge Leitao

ขอบคุณ @JorgeLeitao ที่สมเหตุสมผล ฉันเรียนรู้ YAML และ Jinja2 ด้วยกันในขณะที่พัฒนาเพลย์บุ๊คและเทมเพลต Ansible และไม่สามารถคิดถึงกันได้โดยปราศจากกันและกัน
sm4rk0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.