Làm thế nào để hợp nhất các mảng YAML?


112

Tôi muốn hợp nhất các mảng trong YAML và tải chúng qua ruby ​​-

some_stuff: &some_stuff
 - a
 - b
 - c

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

Tôi muốn có mảng kết hợp là [a,b,c,d,e,f]

Tôi nhận được lỗi: không tìm thấy khóa mong đợi trong khi phân tích cú pháp ánh xạ khối

Làm cách nào để hợp nhất các mảng trong YAML?


6
Tại sao bạn muốn thực hiện việc này bằng YAML hơn là bằng ngôn ngữ bạn đang phân tích cú pháp?
Patrick Collins

7
để khô lên trùng lặp trong một tập tin yaml rất lớn
lfender6445

4
Đây là một thực hành rất tệ. Bạn nên đọc yamls một cách riêng biệt, đặt các mảng lại với nhau trong Ruby, sau đó viết nó trở lại yaml.
sawa

74
Làm thế nào là cố gắng để được thực hành khô khan?
krak3n

13
@PatrickCollins Tôi thấy câu hỏi này đang cố gắng giảm sự trùng lặp trong tệp .gitlab-ci.yml của mình và rất tiếc là tôi không có quyền kiểm soát trình phân tích cú pháp mà GitLab CI sử dụng :(
rink.attendant.

Câu trả lời:


40

Nếu mục đích là chạy một chuỗi lệnh shell, bạn có thể đạt được điều này như sau:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Điều này tương đương với:

some_stuff: "a\nb\nc"

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

Tôi đã sử dụng cái này trên của mình gitlab-ci.yml(để trả lời @ rink.attendant. 6 bình luận về câu hỏi).


Ví dụ làm việc mà chúng tôi sử dụng để hỗ trợ requirements.txtcó các kho lưu trữ riêng tư từ 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...

nơi requirements_test.txtchứa ví dụ

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


3
Tài giỏi. Tôi đang sử dụng nó trong đường dẫn Bitbucket của chúng tôi bây giờ. Cảm ơn
Dariop

* Không bắt buộc phải có dấu gạch ngang ở đây, chỉ cần dấu gạch ở cuối là đủ. * Đây là một giải pháp kém hơn vì khi công việc không thành công trên một câu lệnh nhiều dòng rất dài, không rõ câu lệnh nào bị lỗi.
Mina Luke

1
@MinaLuke, kém hơn so với cái gì? Không có câu trả lời hiện tại nào cung cấp cách hợp nhất hai mục chỉ sử dụng yaml ... Hơn nữa, không có câu hỏi nào nêu rõ rằng OP muốn sử dụng điều này trong CI / CD. Cuối cùng, khi điều này được sử dụng trong CI / CD, việc ghi nhật ký chỉ phụ thuộc vào CI / CD cụ thể được sử dụng, không phụ thuộc vào khai báo yaml. Vì vậy, nếu bất cứ điều gì, CI / CD mà bạn đang đề cập đến là một trong những công việc tồi tệ. Yaml trong câu trả lời này là hợp lệ và giải quyết được vấn đề của OP.
Jorge Leitao

@JorgeLeitao Tôi đoán bạn sử dụng nó để kết hợp các Quy tắc. Bạn có thể cung cấp một ví dụ gitlabci đang hoạt động không? Tôi đã thử một cái gì đó dựa trên giải pháp của bạn, nhưng luôn gặp lỗi xác thực.
niels

@niels, tôi đã thêm một ví dụ với một ví dụ gitlabci đang hoạt động. Lưu ý rằng một số IDE đánh dấu yaml này là không hợp lệ, mặc dù không phải vậy.
Jorge Leitao

26

Cập nhật: 2019-07-01 14:06:12

  • Lưu ý : một câu trả lời khác cho câu hỏi này đã được chỉnh sửa đáng kể với bản cập nhật về các phương pháp thay thế .
    • Câu trả lời được cập nhật đó đề cập đến một giải pháp thay thế cho cách giải quyết trong câu trả lời này. Nó đã được thêm vào phần Xem thêm bên dưới.

Bối cảnh

Bài đăng này giả định bối cảnh sau:

  • python 2.7
  • trình phân tích cú pháp python YAML

Vấn đề

lfender6445 muốn hợp nhất hai hoặc nhiều danh sách trong một tệp YAML và để các danh sách đã hợp nhất đó xuất hiện dưới dạng một danh sách số ít khi được phân tích cú pháp.

Giải pháp (Cách giải quyết)

Điều này có thể đạt được đơn giản bằng cách gán các neo YAML cho các ánh xạ, trong đó các danh sách mong muốn xuất hiện dưới dạng các phần tử con của các ánh xạ. Tuy nhiên, có những lưu ý đối với điều này (xem phần dưới "Cạm bẫy").

Trong ví dụ dưới đây, chúng ta có ba ánh xạ ( list_one, list_two, list_three) và ba neo và bí danh tham chiếu đến những ánh xạ này nếu thích hợp.

Khi tệp YAML được tải trong chương trình, chúng tôi nhận được danh sách mà chúng tôi muốn, nhưng nó có thể yêu cầu sửa đổi một chút sau khi tải (xem các cạm bẫy bên dưới).

Thí dụ

Tệp YAML gốc

  list_one: & id001
   - một
   - b
   - c

  list_two: & id002
   - e
   - f
   - g

  list_three: & id003
   - h
   - Tôi
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Kết quả sau YAML.safe_load

## list_combined
  [
    [
      "a",
      "b",
      "c"
    ],
    [
      "e",
      "f",
      "g"
    ],
    [
      "h",
      "Tôi",
      "j"
    ]
  ]

Cạm bẫy

  • cách tiếp cận này tạo ra một danh sách lồng nhau các danh sách, có thể không phải là kết quả mong muốn chính xác, nhưng điều này có thể được xử lý sau bằng cách sử dụng phương pháp làm phẳng
  • những lưu ý thông thường đối với neo và bí danh YAML áp dụng cho tính duy nhất và thứ tự khai báo

Phần kết luận

Cách tiếp cận này cho phép tạo danh sách hợp nhất bằng cách sử dụng bí danh và tính năng liên kết của YAML.

Mặc dù kết quả đầu ra là một danh sách lồng nhau, nhưng điều này có thể dễ dàng chuyển đổi bằng flattenphương pháp này.

Xem thêm

Phương pháp thay thế được cập nhật bởi @Anthon

Ví dụ về flattenphương pháp


21

Điều này sẽ không hoạt động:

  1. hợp nhất chỉ được hỗ trợ bởi các thông số kỹ thuật YAML cho ánh xạ và không cho chuỗi

  2. bạn hoàn toàn trộn mọi thứ bằng cách có một khóa hợp nhất << theo sau là dấu phân tách khóa / giá trị :và một giá trị là tham chiếu rồi tiếp tục với một danh sách ở cùng mức thụt lề

Điều này không đúng YAML:

combine_stuff:
  x: 1
  - a
  - b

Vì vậy, cú pháp ví dụ của bạn thậm chí sẽ không có ý nghĩa như một đề xuất tiện ích mở rộng YAML.

Nếu bạn muốn làm điều gì đó như hợp nhất nhiều mảng, bạn có thể muốn xem xét một cú pháp như:

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

nơi s1, s2, s3đang neo trên chuỗi (không hiển thị) mà bạn muốn kết hợp thành một chuỗi mới và sau đó có d, ef gắn vào đó. Nhưng YAML đang giải quyết loại cấu trúc độ sâu này trước tiên, vì vậy không có ngữ cảnh thực sự có sẵn trong quá trình xử lý khóa hợp nhất. Không có mảng / danh sách nào có sẵn cho bạn để bạn có thể đính kèm giá trị đã xử lý (chuỗi được cố định) vào.

Bạn có thể thực hiện cách tiếp cận như được đề xuất bởi @dreftymac, nhưng điều này có nhược điểm lớn là bằng cách nào đó bạn cần biết chuỗi lồng nhau nào cần làm phẳng (nghĩa là bằng cách biết "đường dẫn" từ gốc của cấu trúc dữ liệu được tải đến chuỗi mẹ), hoặc bạn thực hiện một cách đệ quy cấu trúc dữ liệu đã tải để tìm kiếm các mảng / danh sách lồng nhau và làm phẳng tất cả chúng một cách bừa bãi.

Một giải pháp tốt hơn IMO sẽ là sử dụng các thẻ để tải các cấu trúc dữ liệu làm phẳng cho bạn. Điều này cho phép biểu thị rõ ràng những gì cần làm phẳng và những gì không và cung cấp cho bạn toàn quyền kiểm soát việc làm phẳng này được thực hiện trong quá trình tải hay được thực hiện trong khi truy cập. Chọn cái nào là vấn đề dễ thực hiện và hiệu quả về thời gian và không gian lưu trữ. Đây là sự đánh đổi tương tự cần được thực hiện để triển khai tính năng khóa hợp nhất và không có giải pháp duy nhất nào luôn là tốt nhất.

Ví dụ: ruamel.yamlthư viện của tôi sử dụng brute force merge-dicts trong quá trình tải khi sử dụng trình tải an toàn của nó, dẫn đến các từ điển được hợp nhất là các loại Python bình thường. Việc hợp nhất này phải được thực hiện trước và sao chép dữ liệu (không gian không hiệu quả) nhưng nhanh chóng trong tra cứu giá trị. Khi sử dụng bộ tải khứ hồi, bạn muốn có thể kết xuất các hợp nhất không hợp nhất, vì vậy chúng cần được giữ riêng biệt. Cơ cấu dữ liệu như dict được tải do tải khứ hồi, hiệu quả về không gian nhưng truy cập chậm hơn, vì nó cần thử và tra cứu một khóa không tìm thấy trong chính dict trong các hợp nhất (và điều này không được lưu vào bộ nhớ đệm, vì vậy nó cần phải được thực hiện mọi lúc). Tất nhiên những cân nhắc như vậy không quan trọng lắm đối với các tệp cấu hình tương đối nhỏ.


Sau đây thực hiện một lược đồ giống như hợp nhất cho các danh sách trong python bằng cách sử dụng các đối tượng có thẻ flatten mà khi di chuyển sẽ tái sinh thành các mục là danh sách và được gắn thẻ toflatten. Sử dụng hai thẻ này, bạn có thể có tệp YAML:

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

(việc sử dụng trình tự kiểu dòng so với khối là hoàn toàn tùy ý và không ảnh hưởng đến kết quả được tải).

Khi lặp lại các mục là giá trị cho khóa, m1điều này "đệ quy" thành các chuỗi được gắn thẻ toflatten, nhưng hiển thị các danh sách khác (bí danh hoặc không) dưới dạng một mục duy nhất.

Một cách khả thi với mã Python để đạt được điều đó là:

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)

kết quả đầu ra:

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

Như bạn có thể thấy, bạn có thể thấy, trong chuỗi cần làm phẳng, bạn có thể sử dụng bí danh cho một chuỗi được gắn thẻ hoặc bạn có thể sử dụng một chuỗi được gắn thẻ. YAML không cho phép bạn làm:

- !flatten *x2

, tức là gắn thẻ một chuỗi được cố định, vì điều này về cơ bản sẽ biến nó thành một cấu trúc dữ liệu khác.

Sử dụng các thẻ rõ ràng IMO tốt hơn là có một số phép thuật xảy ra như với các khóa hợp nhất YAML <<. Nếu không có gì khác, bây giờ bạn phải trải qua vòng lặp nếu bạn tình cờ có tệp YAML với ánh xạ có khóa <<mà bạn không muốn hoạt động như khóa hợp nhất, ví dụ: khi bạn thực hiện ánh xạ các toán tử C với mô tả của chúng bằng tiếng Anh (hoặc một số ngôn ngữ tự nhiên khác).


9

Nếu bạn chỉ cần hợp nhất một mục vào một danh sách, bạn có thể làm

fruit:
  - &banana
    name: banana
    colour: yellow

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

cái nào mang lại

fruit:
  - name: banana
    colour: yellow

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

-4

Bạn có thể hợp nhất các ánh xạ sau đó chuyển đổi các khóa của chúng thành một danh sách, trong các điều kiện sau:

  • nếu bạn đang sử dụng jinja2 templating và
  • nếu thứ tự mặt hàng không quan trọng
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}

Có gì sai với câu trả lời này? Tôi không ngại những lời phản đối nếu chúng được tranh luận. Tôi sẽ giữ câu trả lời cho những người có thể tận dụng nó.
sm4rk0

3
Có thể là vì câu trả lời này dựa trên jinja2 templating, khi câu hỏi yêu cầu thực hiện nó trong yml. jinja2 yêu cầu môi trường Python, môi trường này sẽ phản tác dụng nếu OP đang cố gắng KHÔ. Ngoài ra, nhiều công cụ CI / CD không chấp nhận bước tạo khuôn mẫu.
Jorge Leitao

Cảm ơn @JorgeLeitao. Điều đó có lý. Tôi đã học được YAML và Jinja2 cùng nhau trong khi phát triển playbooks Ansible và các mẫu và không thể suy nghĩ về một mà không có khác
sm4rk0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.