Làm cách nào tôi có thể phân tích tệp YAML từ tập lệnh shell Linux?


192

Tôi muốn cung cấp một tệp cấu hình có cấu trúc dễ dàng nhất có thể cho người dùng không có kỹ thuật chỉnh sửa (không may nó phải là một tệp) và vì vậy tôi muốn sử dụng YAML. Tuy nhiên, tôi không thể tìm thấy bất kỳ cách phân tích cú pháp nào từ tập lệnh shell Unix.


không trực tiếp câu hỏi của bạn, nhưng bạn có thể muốn xem xét khả thi nếu việc kiểm tra vỏ của bạn đặc biệt là xử lý việc quản lý từ xa các nút khác nhau (và kiểm kê yaml)
eckes

9
Hãy thử sử dụng yqđể đọc / ghi các tệp yaml trong shell. Trang của dự án là ở đây: mikefarah.github.io/yq Bạn có thể cài đặt các công cụ với brew, apthoặc tải về nhị phân. Đọc một giá trị đơn giản nhưyq r some.yaml key.value
vdimitrov

@kenorb JSON! = yml / YAML
swe

Tôi tìm thấy các chức năng liên quan chặt chẽ với github của pkuczynski trong đó tốt nhất (đối với tôi) là từ jasperes, được duy trì trong github của riêng anh ta
splaisan

Câu trả lời:


56

Trường hợp sử dụng của tôi có thể hoặc không hoàn toàn giống với những gì bài đăng gốc này đã hỏi, nhưng nó hoàn toàn tương tự.

Tôi cần phải lấy một số YAML làm biến bash. YAML sẽ không bao giờ sâu hơn một cấp.

YAML trông giống như vậy:

KEY:                value
ANOTHER_KEY:        another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY:           last_value

Đầu ra giống như một dis:

KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"

Tôi đã đạt được đầu ra với dòng này:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gtìm :và thay thế nó bằng =", trong khi bỏ qua ://(đối với URL)
  • s/$/"/gnối "vào cuối mỗi dòng
  • s/ *=/=/g loại bỏ tất cả các không gian trước =

13
Không chắc chắn những gì bạn đang nhận được, nhưng nếu bạn có nghĩa là điều này không hoạt động cho tất cả YAML, bạn đã đúng. Đó là lý do tại sao tôi mở ra với một vài bằng cấp. Tôi chỉ chia sẻ những gì làm việc cho trường hợp sử dụng của tôi, vì nó trả lời câu hỏi tốt hơn bất kỳ câu hỏi nào khác vào thời điểm đó. Điều này chắc chắn có thể được mở rộng.
Curtis Blackwell

3
một chút mở để tiêm mã quá, nhưng như bạn đã nói là một bước tiến
Oriettaxx

1
Tôi chỉ từng viết kịch bản shell để sử dụng cục bộ, vì vậy đó không phải là mối quan tâm đối với tôi. Tuy nhiên, nếu bạn biết cách bảo mật và / hoặc muốn giải thích, tôi chắc chắn rất biết ơn.
Curtis Blackwell

2
Yaml một cấp độ sâu có nhiều hình thức - các giá trị có thể được phân chia theo dòng thụt lề; các giá trị có thể được trích dẫn theo nhiều cách mà shell sẽ không phân tích cú pháp; tất cả mọi thứ có thể được viết trên một dòng với dấu ngoặc : {KEY: 'value', ...}; và có thể những người khác. Quan trọng nhất, nếu bạn có ý định đánh giá kết quả là mã shell, điều đó sẽ rất không an toàn.
Beni Cherniavsky-Paskin

280

Đây là một trình phân tích cú pháp chỉ bash tận dụng sed và awk để phân tích các tệp yaml đơn giản:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]}}
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      }
   }'
}

Nó hiểu các tập tin như:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   file: "yes"

Mà, khi phân tích cú pháp bằng cách sử dụng:

parse_yaml sample.yml

sẽ xuất ra:

global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"

nó cũng hiểu các tệp yaml, được tạo bởi ruby ​​có thể bao gồm các biểu tượng ruby, như:

---
:global:
  :debug: 'yes'
  :verbose: 'no'
  :debugging:
    :detailed: 'no'
    :header: debugging started
  :output: 'yes'

và sẽ xuất ra giống như trong ví dụ trước.

sử dụng điển hình trong một tập lệnh là:

eval $(parse_yaml sample.yml)

parse_yaml chấp nhận một đối số tiền tố để tất cả các cài đặt được nhập có một tiền tố chung (sẽ giảm nguy cơ xung đột không gian tên).

parse_yaml sample.yml "CONF_"

sản lượng:

CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"

Lưu ý rằng các cài đặt trước trong tệp có thể được gọi bằng các cài đặt sau:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   debug: $global_debug

Một cách sử dụng hay khác là trước tiên phân tích tệp mặc định và sau đó là cài đặt người dùng, hoạt động vì cài đặt sau ghi đè lên tệp đầu tiên:

eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)

3
Tuyệt vời Stefan! Thật tuyệt vời nếu nó có thể biến -ký hiệu yaml thành mảng bash bản địa!
quickshiftin

3
Điều đó sẽ khá dễ thực hiện nếu bạn thay đổi dòng printf trong tập lệnh awk. Lưu ý rằng mặc dù bash không có hỗ trợ cho các mảng kết hợp đa chiều để bạn kết thúc với một mảng + một khóa duy nhất cho mỗi giá trị. Hmm, có lẽ nên chuyển cái này sang github ...
Stefan Farestam

5
Điều này hy vọng thụt yml tiêu chuẩn của 2 không gian. Nếu bạn đang sử dụng 4 khoảng trắng, thì các biến sẽ nhận được hai dấu gạch dưới là dấu phân cách, ví dụ global__debugthay vì global_debug.
k0pernikus

3
Xin chào vaab - Mặc dù tôi chắc chắn rằng bạn đã chính xác rằng nhiều độc giả muốn phân tích các tệp YAML thực từ shell, nhưng nó không rõ ràng (ít nhất là với tôi) kết quả sẽ như thế nào. Với kịch bản này, tôi đã khắc phục sự cố và xác định một tập hợp con có ánh xạ hợp lý thành các biến tiêu chuẩn. Chắc chắn không có giả vờ nào đã giải quyết vấn đề lớn hơn về phân tích các tệp YAML thực.
Stefan Farestam

3
Nó chỉ in đầu ra trên màn hình. Làm thế nào bạn sẽ truy cập các giá trị sau này?
sattu

96

Tôi đã viết bằng shyamlpython cho nhu cầu truy vấn YAML từ dòng lệnh shell.

Tổng quat:

$ pip install shyaml      ## installation

Ví dụ tệp YAML (với các tính năng phức tạp):

$ cat <<EOF > test.yaml
name: "MyName !!"
subvalue:
    how-much: 1.1
    things:
        - first
        - second
        - third
    other-things: [a, b, c]
    maintainer: "Valentin Lab"
    description: |
        Multiline description:
        Line 1
        Line 2
EOF

Truy vấn cơ bản:

$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab

Truy vấn vòng lặp phức tạp hơn trên các giá trị phức tạp:

$ cat test.yaml | shyaml values-0 | \
  while read -r -d $'\0' value; do
      echo "RECEIVED: '$value'"
  done
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'

Một vài điểm chính:

  • tất cả các kiểu YAML và các cú pháp cú pháp được xử lý chính xác, dưới dạng nhiều chuỗi, chuỗi được trích dẫn, chuỗi nội tuyến ...
  • \0 đầu ra đệm có sẵn cho thao tác nhập đa dòng rắn.
  • ký hiệu chấm đơn giản để chọn các giá trị phụ (nghĩa là: subvalue.maintainerlà một khóa hợp lệ).
  • truy cập theo chỉ mục được cung cấp cho các chuỗi (tức là: subvalue.things.-1là yếu tố cuối cùng của subvalue.thingschuỗi.)
  • truy cập vào tất cả các phần tử trình tự / cấu trúc trong một lần để sử dụng trong các vòng lặp bash.
  • bạn có thể xuất toàn bộ phần con của tệp YAML dưới dạng ... YAML, kết hợp tốt cho các thao tác tiếp theo với shyaml.

Nhiều mẫu và tài liệu có sẵn trên trang github của shyaml hoặc trang PyPI của shyaml .


1
Điều này thật tuyệt! Sẽ thật tuyệt nếu có một lá cờ bỏ qua các giá trị yaml trống trong đầu ra. Ngay bây giờ nó xuất ra "null". Tôi đang sử dụng nó cùng với envdir để xuất tệp soạn thảo docker thành envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket Vui lòng sử dụng trang vấn đề github! Tôi sẽ vui mừng ít nhất để theo dõi điều này. ;)
vaab

1
Thật không may, shyamlchậm
mức

42

yq là bộ xử lý dòng lệnh YAML nhẹ và di động

Mục đích của dự án là jq hoặc sed của các tập tin yaml.

( https://github.com/mikefarah/yq#readme )

Như một ví dụ (bị đánh cắp trực tiếp từ tài liệu ), được cung cấp một tệp sample.yaml của:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples

sau đó

yq r sample.yaml bob.*.cats

sẽ xuất

- bananas
- apples

nó chỉ thiếu khả năng lọc
Antonin

Formulae.brew.sh/formula/yq có 26.679 lượt cài đặt trong năm qua.
Dustinevan

1
@Antonin Tôi không chắc đây có phải ý bạn không nhưng có vẻ như bây giờ nó có một số khả năng lọc: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

Có thể chuyển một tập lệnh nhỏ cho một số trình thông dịch, như Python. Một cách dễ dàng để làm như vậy bằng Ruby và thư viện YAML của nó là như sau:

$ RUBY_SCRIPT="data = YAML::load(STDIN.read); puts data['a']; puts data['b']"
$ echo -e '---\na: 1234\nb: 4321' | ruby -ryaml -e "$RUBY_SCRIPT"
1234
4321

, Ở đâudata là một hàm băm (hoặc mảng) với các giá trị từ yaml.

Như một phần thưởng, nó sẽ phân tích vấn đề trước của Jekyll là tốt.

ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md

1
nó có dùng được không bạn đã đặt yaml bằng tiếng vang cho thông dịch viên ruby. nhưng làm thế nào nên sử dụng biến này trong phần còn lại của tập lệnh bash?
Znik

Vâng, nó có thể sử dụng được. Các RUBY_SCRIPTbiến là một kịch bản ruby có thể được ghi vào một tập tin để thay thế (chạy với ruby -ryaml <rubyscript_filename>). Nó chứa logic để chuyển đổi văn bản đầu vào thành một số văn bản đầu ra, lưu trữ nội dung vào databiến. Tiếng vang xuất ra một văn bản yaml, nhưng bạn có thể sử dụng cat <yaml_filename>để dẫn nội dung của tệp thay thế.
Rafael

Tôi xin lỗi nhưng tôi không thấy điều này trong ví dụ trên. Ở biến đầu tiên RUBY_SCRIPT giữ mã cho trình thông dịch ruby. Tiếng vang tiếp theo -e mô phỏng bất kỳ dữ liệu yaml nào, đây là bằng cách chuyển hướng đống thành trình thông dịch ruby. Điều này gọi mã ruby ​​là tập lệnh nội tuyến và cuối cùng in ra các ví dụ đầu ra 'a' và 'b'. Sau đó, biến tải vào bash cho mã thực thi còn lại của mình ở đâu? Tôi chỉ thấy một cách giải quyết. đưa ruby ​​outout vào tạm_file, điều đó sẽ tạo ra các dòng: biến = 'value' và sau đó tải nó vào bash by '. tập tin tạm thời'. nhưng đây là cách giải quyết chứ không phải giải quyết.
Znik

1
@Znik một khi bạn đã có thứ gì đó trên thiết bị xuất chuẩn, được sản xuất bởi thứ gì đó được nuôi bằng stdin, phần còn lại phụ thuộc vào tay bash (và như một lời nhắc nhở, nếu bạn cần stdoutđược cung cấp biến, bạn không cần phải dựa vào tập tin tạm thời! sử dụng x=$(...)hoặc thậm chí read a b c < <(...)). Vì vậy, đây là một giải pháp hợp lệ khi bạn biết chính xác những gì bạn muốn tìm nạp trong tệp YAML và biết cách viết các dòng ruby ​​để truy cập dữ liệu này. Ngay cả khi nó là thô, nó là một bằng chứng đầy đủ về khái niệm ý tưởng IMHO. Tuy nhiên, sự thật là nó không cung cấp cho bạn một bản tóm tắt đầy đủ.
vaab

Vâng, đúng vậy. Bạn thật cứng nhắc. Thak bạn cho mánh khóe đó. Sử dụng một biến là đơn giản. nhưng nhiều cảnh báo không có. Thủ thuật với danh sách biến đọc << (thực thi đến thiết bị xuất chuẩn) rất hữu ích :)
Znik

23

Cho rằng Python3 và PyYAML là những phụ thuộc khá dễ gặp hiện nay, những điều sau đây có thể giúp:

yaml() {
    python3 -c "import yaml;print(yaml.safe_load(open('$1'))$2)"
}

VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")

Tôi yêu shyaml, nhưng trên các hệ thống bị ngắt kết nối thì đây là một trình cứu sinh. Nên hoạt động với phần lớn python2, ví dụ, RHEL.
rsaw

2
Có thể sử dụng yaml.safe_loadvì nó an toàn hơn. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

12

đây là phiên bản mở rộng của câu trả lời của Stefan Farestam:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|,$s\]$s\$|]|" \
        -e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1  - \4|;t1" \
        -e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1  - \3|;p" $1 | \
   sed -ne "s|,$s}$s\$|}|" \
        -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1  \3: \4|;t1" \
        -e    "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1  \2|;p" | \
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
        -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | \
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
      if(length($2)== 0){  vname[indent]= ++idx[indent] };
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
      }
   }'
}

Phiên bản này hỗ trợ -ký hiệu và ký hiệu ngắn cho từ điển và danh sách. Đầu vào sau:

global:
  input:
    - "main.c"
    - "main.h"
  flags: [ "-O3", "-fpic" ]
  sample_input:
    -  { property1: value, property2: "value2" }
    -  { property1: "value3", property2: 'value 4' }

tạo đầu ra này:

global_input_1="main.c"
global_input_2="main.h"
global_flags_1="-O3"
global_flags_2="-fpic"
global_sample_input_1_property1="value"
global_sample_input_1_property2="value2"
global_sample_input_2_property1="value3"
global_sample_input_2_property2="value 4"

như bạn có thể thấy các -mục tự động được đánh số để có được các tên biến khác nhau cho mỗi mục. Trongbash đó không có mảng đa chiều, vì vậy đây là một cách để làm việc xung quanh. Nhiều cấp độ được hỗ trợ. Để giải quyết vấn đề với các khoảng trắng ở cuối được đề cập bởi @briceburg, người ta phải đặt các giá trị trong dấu ngoặc đơn hoặc dấu ngoặc kép. Tuy nhiên, vẫn còn một số hạn chế: Việc mở rộng từ điển và danh sách có thể tạo ra kết quả sai khi các giá trị chứa dấu phẩy. Ngoài ra, các cấu trúc phức tạp hơn như các giá trị trải dài trên nhiều dòng (như khóa ssh) không được hỗ trợ (chưa).

Một vài từ về mã: Lệnh đầu tiên sedmở rộng dạng từ điển ngắn thành { key: value, ...}thông thường và chuyển đổi chúng thành kiểu yaml đơn giản hơn. Cuộc sedgọi thứ hai thực hiện tương tự đối với ký hiệu ngắn của danh sách và chuyển đổi [ entry, ... ]thành danh sách được ghi thành từng mục với -ký hiệu. Cuộc sedgọi thứ ba là cuộc gọi ban đầu xử lý các từ điển thông thường, bây giờ với việc bổ sung để xử lý các danh sách có -và thụt lề. Phần awkgiới thiệu một chỉ mục cho mỗi cấp độ thụt đầu dòng và tăng nó khi tên biến trống (tức là khi xử lý một danh sách). Giá trị hiện tại của các bộ đếm được sử dụng thay vì vname trống. Khi đi lên một cấp, các quầy đều bằng không.

Chỉnh sửa: Tôi đã tạo một kho lưu trữ github cho việc này.


11

Khó nói vì nó phụ thuộc vào những gì bạn muốn trình phân tích cú pháp trích xuất từ ​​tài liệu YAML của bạn. Đối với trường hợp đơn giản, bạn có thể có thể sử dụng grep, cut, awkvv Để biết thêm phân tích cú pháp phức tạp bạn sẽ cần phải sử dụng một full-blown phân tích thư viện như Python của PyYAML hoặc YAML :: Perl .


11

Tôi vừa viết một trình phân tích cú pháp mà tôi gọi là Yay! ( Yaml không phải là Yamlesque! ) Phân tích Yamlesque , một tập hợp nhỏ của YAML. Vì vậy, nếu bạn đang tìm kiếm một trình phân tích cú pháp YAML tuân thủ 100% cho Bash thì đây không phải là nó. Tuy nhiên, để trích dẫn OP, nếu bạn muốn một tệp cấu hình có cấu trúc dễ dàng nhất có thể cho người dùng không có kỹ thuật chỉnh sửa giống như YAML, điều này có thể được quan tâm.

Nó được đưa ra bởi câu trả lời trước đó nhưng viết các mảng kết hợp ( vâng, nó yêu cầu Bash 4.x ) thay vì các biến cơ bản. Nó làm như vậy theo cách cho phép dữ liệu được phân tích cú pháp mà không cần biết trước về các khóa để có thể viết mã điều khiển dữ liệu.

Cũng như các thành phần mảng khóa / giá trị, mỗi mảng có một keysmảng chứa danh sách các tên khóa, một childrenmảng chứa tên của mảng con và một parentkhóa tham chiếu đến mẹ của nó.

Đây là một ví dụ về Yamlesque:

root_key1: this is value one
root_key2: "this is value two"

drink:
  state: liquid
  coffee:
    best_served: hot
    colour: brown
  orange_juice:
    best_served: cold
    colour: orange

food:
  state: solid
  apple_pie:
    best_served: warm

root_key_3: this is value three

Dưới đây là một ví dụ cho thấy cách sử dụng nó:

#!/bin/bash
# An example showing how to use Yay

. /usr/lib/yay

# helper to get array value at key
value() { eval echo \${$1[$2]}; }

# print a data collection
print_collection() {
  for k in $(value $1 keys)
  do
    echo "$2$k = $(value $1 $k)"
  done

  for c in $(value $1 children)
  do
    echo -e "$2$c\n$2{"
    print_collection $c "  $2"
    echo "$2}"
  done
}

yay example
print_collection example

đầu ra nào:

root_key1 = this is value one
root_key2 = this is value two
root_key_3 = this is value three
example_drink
{
  state = liquid
  example_coffee
  {
    best_served = hot
    colour = brown
  }
  example_orange_juice
  {
    best_served = cold
    colour = orange
  }
}
example_food
{
  state = solid
  example_apple_pie
  {
    best_served = warm
  }
}

đây là trình phân tích cú pháp:

yay_parse() {

   # find input file
   for f in "$1" "$1.yay" "$1.yml"
   do
     [[ -f "$f" ]] && input="$f" && break
   done
   [[ -z "$input" ]] && exit 1

   # use given dataset prefix or imply from file name
   [[ -n "$2" ]] && local prefix="$2" || {
     local prefix=$(basename "$input"); prefix=${prefix%.*}
   }

   echo "declare -g -A $prefix;"

   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
          -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
   awk -F$fs '{
      indent       = length($1)/2;
      key          = $2;
      value        = $3;

      # No prefix or parent for the top level (indent zero)
      root_prefix  = "'$prefix'_";
      if (indent ==0 ) {
        prefix = "";          parent_key = "'$prefix'";
      } else {
        prefix = root_prefix; parent_key = keys[indent-1];
      }

      keys[indent] = key;

      # remove keys left behind if prior row was indented more than this row
      for (i in keys) {if (i > indent) {delete keys[i]}}

      if (length(value) > 0) {
         # value
         printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
         printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
      } else {
         # collection
         printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
         printf("declare -g -A %s%s;\n", root_prefix, key);
         printf("%s%s[parent]=\"%s%s\";\n", root_prefix, key, prefix, parent_key);
      }
   }'
}

# helper to load yay data file
yay() { eval $(yay_parse "$@"); }

Có một số tài liệu trong tệp nguồn được liên kết và dưới đây là một lời giải thích ngắn về những gì mã làm.

Trước yay_parsetiên, hàm định vị inputtệp hoặc thoát với trạng thái thoát là 1. Tiếp theo, nó xác định tập dữ liệu prefix, được chỉ định rõ ràng hoặc xuất phát từ tên tệp.

Nó ghi bashcác lệnh hợp lệ vào đầu ra tiêu chuẩn của nó, nếu được thực thi, xác định các mảng biểu thị nội dung của tệp dữ liệu đầu vào. Đầu tiên trong số này xác định mảng cấp cao nhất:

echo "declare -g -A $prefix;"

Lưu ý rằng các khai báo mảng là kết hợp ( -A) là một tính năng của Bash phiên bản 4. Các khai báo cũng là toàn cục ( -g) để chúng có thể được thực thi trong một hàm nhưng có sẵn cho phạm vi toàn cầu như trình yaytrợ giúp:

yay() { eval $(yay_parse "$@"); }

Dữ liệu đầu vào được xử lý ban đầu với sed. Nó bỏ các dòng không khớp với đặc tả định dạng Yamlesque trước khi phân định các trường Yamlesque hợp lệ bằng ký tự phân tách tệp ASCII và xóa bất kỳ dấu ngoặc kép nào xung quanh trường giá trị.

 local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
 sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |

Hai biểu thức tương tự nhau; chúng chỉ khác nhau bởi vì cái đầu tiên chọn ra các giá trị được trích dẫn trong khi cái thứ hai chọn ra các giá trị không được trích dẫn.

Các tập tin Separator (28 / hex 12 / bát phân 034) được sử dụng bởi vì, như một nhân vật không thể in được, nó dường như không có trong dữ liệu đầu vào.

Kết quả được dẫn vào awktrong đó xử lý một dòng đầu vào của nó tại một thời điểm. Nó sử dụng ký tự FS để gán từng trường cho một biến:

indent       = length($1)/2;
key          = $2;
value        = $3;

Tất cả các dòng có một thụt lề (có thể bằng 0) và một khóa nhưng chúng không có giá trị. Nó tính toán một mức thụt lề cho dòng chia chiều dài của trường đầu tiên, chứa khoảng trắng hàng đầu, bằng hai. Các mục cấp cao nhất mà không có bất kỳ thụt lề nào ở mức không thụt lề.

Tiếp theo, nó tìm ra những gì prefixđể sử dụng cho các mục hiện tại. Đây là những gì được thêm vào một tên khóa để tạo một tên mảng. Có một root_prefixmảng cấp cao nhất được xác định là tên tập dữ liệu và dấu gạch dưới:

root_prefix  = "'$prefix'_";
if (indent ==0 ) {
  prefix = "";          parent_key = "'$prefix'";
} else {
  prefix = root_prefix; parent_key = keys[indent-1];
}

Khóa parent_keylà mức thụt ở trên mức thụt dòng của dòng hiện tại và thể hiện bộ sưu tập mà dòng hiện tại là một phần của. Các cặp khóa / giá trị của bộ sưu tập sẽ được lưu trữ trong một mảng với tên được định nghĩa là nối của prefixparent_key.

Đối với cấp cao nhất (mức thụt lề 0), tiền tố của tập dữ liệu được sử dụng làm khóa chính để nó không có tiền tố (được đặt thành ""). Tất cả các mảng khác đều có tiền tố gốc.

Tiếp theo, khóa hiện tại được chèn vào một mảng (awk-Internal) có chứa các khóa. Mảng này tồn tại trong suốt toàn bộ phiên awk và do đó chứa các khóa được chèn bởi các dòng trước. Khóa được chèn vào mảng bằng cách sử dụng thụt lề làm chỉ mục mảng.

keys[indent] = key;

Vì mảng này chứa các khóa từ các dòng trước đó, bất kỳ khóa nào có mức độ thụt lề so với mức thụt dòng của dòng hiện tại sẽ bị xóa:

 for (i in keys) {if (i > indent) {delete keys[i]}}

Điều này để lại mảng khóa chứa chuỗi khóa từ gốc ở mức thụt 0 đến dòng hiện tại. Nó loại bỏ các khóa cũ vẫn còn khi dòng trước được thụt sâu hơn dòng hiện tại.

Phần cuối cùng xuất ra các bashlệnh: một dòng đầu vào không có giá trị bắt đầu một mức thụt lề mới (một bộ sưu tập theo cách nói YAML) và một dòng đầu vào có giá trị thêm một khóa vào bộ sưu tập hiện tại.

Tên của bộ sưu tập là sự ghép nối của dòng hiện tại prefixparent_key.

Khi một khóa có một giá trị, một khóa có giá trị đó được gán cho bộ sưu tập hiện tại như thế này:

printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);

Câu lệnh đầu tiên xuất ra lệnh để gán giá trị cho một phần tử mảng kết hợp được đặt tên theo khóa và câu lệnh thứ hai xuất lệnh để thêm khóa vào keysdanh sách được phân cách bằng dấu cách của bộ sưu tập :

<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";

Khi khóa không có giá trị, bộ sưu tập mới sẽ được bắt đầu như sau:

printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);

Câu lệnh đầu tiên đưa ra lệnh để thêm bộ sưu tập mới vào childrendanh sách giới hạn không gian của bộ sưu tập hiện tại và câu lệnh thứ hai xuất ra lệnh để khai báo một mảng kết hợp mới cho bộ sưu tập mới:

<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;

Tất cả các đầu ra từ yay_parsecó thể được phân tích cú pháp dưới dạng các lệnh bash bằng các lệnh bash evalhoặc tích sourcehợp.


Bạn đã xem việc biến nó thành một dự án trên GitHub chưa? Hay là nó đã có?
daniel

@daniel, nó ở trong GitHub nhưng không phải trong repo của riêng nó - bạn có thể tìm thấy nó ở đây . Xem examplesusr/libthư mục, Chúng được liên kết trong câu trả lời của tôi cho câu hỏi. Nếu có hứng thú tôi có thể chia nó thành repo của riêng nó.
starfry 23/2/2016

4
Kudos trên YAY. Lúc đầu, tôi viết lại nó thành bash thuần túy, nhưng sau đó tôi không thể dừng bản thân mình và thực hiện lại nó như một trình phân tích cú pháp cơ bản với sự hỗ trợ cho các mảng và các cấu trúc lồng nhau không thể đặt tên của nhau. Đó là tại github.com/binaryphile/y2s .
Phile nhị phân

5
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh

chỉ hữu ích cho cấu hình phẳng. nó không áp dụng cho yaml có cấu trúc. khác, làm thế nào để ngăn chặn sử dụng file.sh tạm thời?
Znik

5

Một tùy chọn khác là chuyển đổi YAML sang JSON, sau đó sử dụng jq để tương tác với biểu diễn JSON hoặc để trích xuất thông tin từ nó hoặc chỉnh sửa nó.

Tôi đã viết một tập lệnh bash đơn giản có chứa keo này - xem dự án Y2J trên GitHub


2

Nếu bạn cần một giá trị duy nhất, bạn có thể một công cụ chuyển đổi tài liệu YAML của bạn thành JSON và cung cấp jq, chẳng hạn yq.

Nội dung của mẫu.yaml:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples
  thing:
    cats: oranges

Thí dụ:

$ yq -r '.bob["thing"]["cats"]' sample.yaml 
oranges

1

Tôi biết điều này rất cụ thể, nhưng tôi nghĩ câu trả lời của tôi có thể hữu ích cho một số người dùng nhất định.
Nếu bạn có nodenpmcài đặt trên máy của bạn, bạn có thể sử dụng js-yaml.
Cài đặt đầu tiên:

npm i -g js-yaml
# or locally
npm i js-yaml

sau đó trong tập lệnh bash của bạn

#!/bin/bash
js-yaml your-yaml-file.yml

Ngoài ra nếu bạn đang sử dụng, jqbạn có thể làm một cái gì đó như thế

#!/bin/bash
json="$(js-yaml your-yaml-file.yml)"
aproperty="$(jq '.apropery' <<< "$json")"
echo "$aproperty"

Bởi vì js-yamlchuyển đổi một tập tin yaml thành một chuỗi json bằng chữ. Sau đó, bạn có thể sử dụng chuỗi với bất kỳ trình phân tích cú pháp json nào trong hệ thống unix của bạn.


1

Nếu bạn có python 2 và PyYAML, bạn có thể sử dụng trình phân tích cú pháp mà tôi đã viết có tên là parse_yaml.py . Một số điều gọn gàng hơn là cho phép bạn chọn một tiền tố (trong trường hợp bạn có nhiều hơn một tệp có các biến tương tự) và để chọn một giá trị duy nhất từ ​​tệp yaml.

Ví dụ: nếu bạn có các tệp yaml này:

dàn dựng.yaml:

db:
    type: sqllite
    host: 127.0.0.1
    user: dev
    password: password123

prod.yaml:

db:
    type: postgres
    host: 10.0.50.100
    user: postgres
    password: password123

Bạn có thể tải cả hai mà không có xung đột.

$ eval $(python parse_yaml.py prod.yaml --prefix prod --cap)
$ eval $(python parse_yaml.py staging.yaml --prefix stg --cap)
$ echo $PROD_DB_HOST
10.0.50.100
$ echo $STG_DB_HOST
127.0.0.1

Và thậm chí anh đào chọn các giá trị bạn muốn.

$ prod_user=$(python parse_yaml.py prod.yaml --get db_user)
$ prod_port=$(python parse_yaml.py prod.yaml --get db_port --default 5432)
$ echo prod_user
postgres
$ echo prod_port
5432

1

Bạn có thể sử dụng một tương đương của YQ mà được viết bằng golang:

./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version

trả về:

62.0.3

0

Bạn cũng có thể xem xét sử dụng Grunt (Trình chạy tác vụ JavaScript). Có thể dễ dàng tích hợp với vỏ. Nó hỗ trợ đọc các tệp YAML ( grunt.file.readYAML) và JSON ( grunt.file.readJSON).

Điều này có thể đạt được bằng cách tạo một tác vụ trong Gruntfile.js(hoặc Gruntfile.coffee), ví dụ:

module.exports = function (grunt) {

    grunt.registerTask('foo', ['load_yml']);

    grunt.registerTask('load_yml', function () {
        var data = grunt.file.readYAML('foo.yml');
        Object.keys(data).forEach(function (g) {
          // ... switch (g) { case 'my_key':
        });
    });

};

sau đó từ shell chỉ đơn giản là chạy grunt foo(kiểm tra grunt --helpcác tác vụ có sẵn).

Hơn nữa, bạn có thể thực hiện exec:foocác tác vụ ( grunt-exec) với các biến đầu vào được truyền từ tác vụ của bạn ( foo: { cmd: 'echo bar <%= foo %>' }) để in đầu ra ở bất kỳ định dạng nào bạn muốn, sau đó chuyển nó sang một lệnh khác.


Ngoài ra còn có công cụ tương tự như Grunt, nó được gọi là gulp với plugin bổ sung gulp-yaml .

Cài đặt qua: npm install --save-dev gulp-yaml

Sử dụng mẫu:

var yaml = require('gulp-yaml');

gulp.src('./src/*.yml')
  .pipe(yaml())
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ space: 2 }))
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ safe: true }))
  .pipe(gulp.dest('./dist/'))

Để có thêm tùy chọn để xử lý định dạng YAML , hãy kiểm tra trang YAML để biết các dự án, thư viện và các tài nguyên khác có thể giúp bạn phân tích định dạng đó.


Các công cụ khác:

  • Jshon

    phân tích cú pháp, đọc và tạo JSON


0

Tôi biết câu trả lời của tôi là cụ thể, nhưng nếu một cái đã được cài đặt PHPSymfony , thì có thể rất thuận tiện để sử dụng trình phân tích cú pháp YAML của Symfony.

Ví dụ:

php -r "require '$SYMFONY_ROOT_PATH/vendor/autoload.php'; \
    var_dump(\Symfony\Component\Yaml\Yaml::parse(file_get_contents('$YAML_FILE_PATH')));"

Ở đây tôi chỉ đơn giản được sử dụng var_dumpđể xuất mảng được phân tích cú pháp nhưng tất nhiên bạn có thể làm nhiều hơn nữa ... :)

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.