ฉันจะแยกไฟล์ YAML จากสคริปต์เชลล์ Linux ได้อย่างไร


193

ฉันต้องการให้ไฟล์การกำหนดค่าที่มีโครงสร้างซึ่งเป็นเรื่องง่ายที่สุดสำหรับผู้ใช้ที่ไม่ใช่ด้านเทคนิคในการแก้ไข (น่าเสียดายที่ต้องเป็นไฟล์) และดังนั้นฉันจึงต้องการใช้ YAML ฉันไม่พบวิธีการวิเคราะห์คำนี้จาก Unix shell script


ไม่ได้โดยตรงคำถามของคุณ แต่คุณอาจต้องการที่จะดูที่เบิ้ลถ้า scriting เปลือกของคุณโดยเฉพาะอย่างยิ่งเกี่ยวกับการจัดการกับการจัดการระยะไกลของโหนดที่แตกต่างกัน (และสินค้าคงคลัง yaml ก)
Eckes

9
ลองใช้yqเพื่ออ่าน / เขียนไฟล์ yaml ในเชลล์ หน้าโครงการอยู่ที่นี่: mikefarah.github.io/yqคุณสามารถติดตั้งเครื่องมือที่มีbrew, aptหรือดาวน์โหลดไบนารี การอ่านค่าทำได้ง่ายเพียงyq r some.yaml key.value
vdimitrov

@kenorb JSON = YML / YAML!
SWE

ผมพบว่าเกี่ยวข้องอย่างใกล้ชิดฟังก์ชั่นGitHub pkuczynski ของที่ดีที่สุด (สำหรับผม) ก็คือว่าจากjasperes ของการบำรุงรักษาใน GitHub ของตัวเอง
splaisan

คำตอบ:


56

กรณีการใช้งานของฉันอาจจะเหมือนกันหรือไม่เหมือนกับที่โพสต์ต้นฉบับนี้ถาม แต่มันก็คล้ายกันมาก

ฉันต้องการดึง YAML บางตัวเป็นตัวแปรทุบตี YAML จะไม่ลึกเกินกว่าหนึ่งระดับ

YAML ดูเหมือนว่า:

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

ผลลัพธ์เช่นดิสก์:

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

ฉันบรรลุผลลัพธ์ด้วยบรรทัดนี้

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gค้นหา:และแทนที่ด้วย="ขณะที่ละเว้น://(สำหรับ URL)
  • s/$/"/gต่อท้าย"ส่วนท้ายของแต่ละบรรทัด
  • s/ *=/=/g ลบช่องว่างทั้งหมดก่อน =

13
ไม่แน่ใจว่าคุณกำลังทำอะไร แต่ถ้าคุณหมายความว่าสิ่งนี้ไม่ได้ผลสำหรับ YAML ทุกคนคุณพูดถูก นั่นเป็นเหตุผลที่ฉันเปิดด้วยคุณสมบัติบางประการ ฉันเพิ่งแชร์สิ่งที่ใช้ได้กับกรณีการใช้งานของฉันเพราะมันตอบคำถามได้ดีกว่าคำถามอื่น ๆ ในเวลานั้น สิ่งนี้สามารถขยายได้อย่างแน่นอน
Curtis Blackwell

3
บิตเปิดให้ฉีดรหัสเช่นกัน แต่ตามที่คุณพูดไปข้างหน้า
Oriettaxx

1
ฉันเคยเขียนเชลล์สคริปเพื่อใช้ในเครื่องเท่านั้นดังนั้นจึงไม่เป็นปัญหาสำหรับฉัน อย่างไรก็ตามหากคุณรู้วิธีการรักษาความปลอดภัยและ / หรือต้องการที่จะทำอย่างละเอียดฉันจะขอบคุณอย่างแน่นอน
Curtis Blackwell

2
yaml ระดับลึกหนึ่งระดับมีหลายรูปแบบ - ค่าสามารถแยกออกเป็นเส้นเยื้องต่อไปนี้ ค่าสามารถอ้างอิงได้หลายวิธีเชลล์จะไม่แยกวิเคราะห์ ทุกอย่างสามารถเขียนได้ในหนึ่งบรรทัดด้วยเครื่องหมายวงเล็บ: {KEY: 'value', ...}; และคนอื่น ๆ ที่สำคัญที่สุดถ้าคุณตั้งใจจะประเมินผลลัพธ์เป็นรหัสเชลล์นั่นจะไม่ปลอดภัยอย่างยิ่ง
Beni Cherniavsky-Paskin

281

นี่คือตัวแยกวิเคราะห์ bash เท่านั้นที่ใช้ประโยชน์จาก sed และ awk เพื่อแยกวิเคราะห์ไฟล์ yaml อย่างง่าย:

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);
      }
   }'
}

มันเข้าใจไฟล์ต่างๆเช่น:

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

## output
output:
   file: "yes"

ซึ่งเมื่อแยกวิเคราะห์โดยใช้:

parse_yaml sample.yml

จะส่งออก:

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

มันยังเข้าใจไฟล์ yaml ซึ่งสร้างโดย ruby ​​ซึ่งอาจรวมถึงสัญลักษณ์ ruby ​​เช่น:

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

และจะส่งออกเช่นเดียวกับในตัวอย่างก่อนหน้า

การใช้งานทั่วไปภายในสคริปต์คือ:

eval $(parse_yaml sample.yml)

parse_yaml ยอมรับอาร์กิวเมนต์คำนำหน้าเพื่อให้การตั้งค่าที่นำเข้าทั้งหมดมีคำนำหน้าทั่วไป (ซึ่งจะลดความเสี่ยงของการชนของเนมสเปซ)

parse_yaml sample.yml "CONF_"

อัตราผลตอบแทน:

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

โปรดทราบว่าการตั้งค่าก่อนหน้าในไฟล์สามารถอ้างถึงได้โดยการตั้งค่าในภายหลัง:

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

## output
output:
   debug: $global_debug

การใช้งานที่ดีอีกอย่างหนึ่งคือการแยกวิเคราะห์ไฟล์เริ่มต้นจากนั้นการตั้งค่าผู้ใช้ซึ่งใช้งานได้เนื่องจากการตั้งค่าหลังจะแทนที่ไฟล์แรก:

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

3
สเตฟานเจ๋ง! มันจะน่าทึ่งถ้ามันสามารถเปลี่ยน-สัญลักษณ์yaml ให้เป็นอาร์เรย์ทุบตีดั้งเดิมได้เช่นกัน!
quickshiftin

3
ซึ่งน่าจะทำได้ง่ายหากคุณเปลี่ยนบรรทัด printf ในสคริปต์ awk โปรดทราบว่า bash นั้นไม่รองรับอาร์เรย์เชื่อมโยงหลายมิติดังนั้นคุณจึงต้องจบลงด้วยอาร์เรย์ + คีย์เดียวต่อค่า อืมน่าจะย้ายไปที่
gitub

5
สิ่งนี้คาดว่าเยื้องมาตรฐาน yml ของ 2 ช่องว่าง หากคุณกำลังใช้ 4 ช่องว่างแล้วตัวแปรจะได้รับสองขีดเป็นตัวคั่นเช่นแทนglobal__debug global_debug
k0pernikus

3
สวัสดี vaab - ในขณะที่ฉันแน่ใจว่าคุณถูกต้องที่ผู้อ่านหลายคนต้องการแยกไฟล์ YAML จริงจากเชลล์มันไม่ชัดเจน (อย่างน้อยสำหรับฉัน) สิ่งที่ผลลัพธ์จะเป็น ด้วยสคริปต์นี้ฉันได้ทำการแทงที่ปัญหาและกำหนดเซตย่อยที่มีการแมปที่เหมาะสมในตัวแปรมาตรฐาน แน่นอนว่าไม่มีข้ออ้างที่จะกล่าวถึงปัญหาที่ใหญ่กว่าของการแยกไฟล์ YAML จริง
Stefan Farestam

3
มันพิมพ์เฉพาะผลลัพธ์บนหน้าจอ คุณจะเข้าถึงค่าในภายหลังได้อย่างไร
sattu

96

ฉันเขียนด้วยภาษาshyamlไพ ธ อนสำหรับความต้องการการสืบค้น YAML จากบรรทัดคำสั่งเชลล์

ข้อมูลทั่วไป:

$ pip install shyaml      ## installation

ตัวอย่างของไฟล์ YAML (ที่มีคุณสมบัติที่ซับซ้อน):

$ 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

แบบสอบถามพื้นฐาน:

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

แบบสอบถามวนลูปที่ซับซ้อนมากขึ้นกับค่าที่ซับซ้อน:

$ 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'

ประเด็นสำคัญบางประการ:

  • YAML ทุกประเภทและความผิดปกติทางไวยากรณ์ได้รับการจัดการอย่างถูกต้องเป็น multiline, สตริงที่ยกมา, ลำดับแบบอินไลน์ ...
  • \0 เอาท์พุทเบาะมีไว้สำหรับการจัดการรายการหลายของแข็ง
  • สัญกรณ์ประแบบง่ายเพื่อเลือกค่าย่อย (เช่น: subvalue.maintainerเป็นคีย์ที่ถูกต้อง)
  • การเข้าถึงโดยดัชนีมีให้กับลำดับ (เช่น: subvalue.things.-1เป็นองค์ประกอบสุดท้ายของsubvalue.thingsลำดับ)
  • เข้าถึงองค์ประกอบลำดับ / struct ทั้งหมดในครั้งเดียวเพื่อใช้ใน bash ลูป
  • คุณสามารถส่งออกส่วนย่อยทั้งหมดของไฟล์ YAML เป็น ... YAML ซึ่งผสมผสานกันอย่างดีสำหรับการจัดการเพิ่มเติมกับ shyaml

ตัวอย่างเพิ่มเติมและเอกสารที่มีอยู่บนหน้า shyaml GitHubหรือshyaml หน้า


1
นี่มันเจ๋งมาก! จะดีถ้ามีการตั้งค่าสถานะเพื่อละเว้นค่า yaml ที่ว่างในผลลัพธ์ ตอนนี้มันจะแสดงผลเป็น "null" ฉันใช้มันพร้อมกับ envdir เพื่อส่งออกไฟล์ประกอบนักเทียบท่าเพื่อ envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket กรุณาใช้หน้าปัญหา github! ฉันจะดีใจอย่างน้อยก็เพื่อติดตามเรื่องนี้ ;)
vaab

1
น่าเสียดายที่shyamlมันช้าอย่างน่าขัน
58

43

yqเป็นตัวประมวลผลบรรทัดคำสั่ง YAML แบบพกพาน้ำหนักเบาและพกพาได้

จุดมุ่งหมายของโครงการคือการเป็นJQหรือ sed ไฟล์ yaml

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

เป็นตัวอย่าง (ขโมยโดยตรงจากเอกสาร ) ให้ไฟล์ sample.yaml ของ:

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

แล้วก็

yq r sample.yaml bob.*.cats

จะส่งออก

- bananas
- apples

มันเป็นเพียงการขาดความสามารถในการกรอง
Antonin

formulae.brew.sh/formula/yqมีการติดตั้ง 26,679 ครั้งในช่วงปีที่ผ่านมา
dustinevan

1
@ Antonin ฉันไม่แน่ใจว่านี่คือสิ่งที่คุณหมายถึง แต่ดูเหมือนว่ามันมีความสามารถในการกรองบางอย่างในขณะนี้: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

เป็นไปได้ที่จะส่งสคริปต์ขนาดเล็กไปยังล่ามบางคนเช่น Python วิธีง่ายๆในการใช้ Ruby และไลบรารี YAML คือ:

$ 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

ซึ่งdataเป็นแฮช (หรืออาร์เรย์) ที่มีค่าจาก yaml

เป็นโบนัสมันจะแยกหน้าของ Jekyll ไม่เป็นไร

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

1
มันใช้งานได้หรือไม่ คุณใส่มันด้วย echo เพื่อ ruby ​​interpreter แต่ควรใช้ตัวแปรนี้อย่างไรภายใต้สคริปต์ทุบตีที่เหลือ?
Znik

ใช่มันใช้งานได้ RUBY_SCRIPTตัวแปรเป็นสคริปต์ทับทิมซึ่งอาจจะเขียนไปยังแฟ้มแทน (ทำงานด้วยruby -ryaml <rubyscript_filename>) มันมีตรรกะในการแปลงข้อความที่ป้อนเข้าเป็นข้อความที่ส่งออกบางส่วนการจัดเก็บเนื้อหาภายในเป็นdataตัวแปร เสียงก้องแสดงข้อความ yaml แต่คุณสามารถใช้cat <yaml_filename>เพื่อไพพ์เนื้อหาของไฟล์แทน
Rafael

ฉันขอโทษ แต่ฉันไม่เห็นสิ่งนี้ในตัวอย่างด้านบน ที่ตัวแปรแรก RUBY_SCRIPT เก็บรหัสไว้สำหรับล่ามทับทิม echo -e ถัดไปจำลองข้อมูล yaml ใด ๆ นี่คือโดยการเปลี่ยนเส้นทางกองไปยังล่ามทับทิม สิ่งนี้เรียกว่าโค้ด ruby ​​เป็นสคริปต์แบบอินไลน์และสุดท้ายพิมพ์ไปยังตัวอย่างผลลัพธ์ 'a' และ 'b' ตัวแปร จากนั้นตัวแปรโหลดลงใน bash สำหรับโค้ดที่ทำงานได้ของเขาที่เหลืออยู่ที่ไหน? ฉันเห็นเพียงวิธีเดียวเท่านั้น ใส่ Ruby outout ลงใน temporary_file ซึ่งควรมีบรรทัด: variable = 'value' และหลังจากนั้นโหลดลงใน bash โดย ' temporary_file' แต่นี่เป็นวิธีแก้ปัญหาไม่ใช่วิธีแก้ปัญหา
Znik

1
@ Znik เมื่อคุณมีบางอย่างใน stdout ที่ผลิตโดยบางสิ่งที่ป้อนด้วย stdin ส่วนที่เหลือจะอาศัยอยู่ในมือของ bash coder (และเป็นเครื่องเตือนใจหากคุณต้องการstdoutให้ป้อนเข้าสู่ตัวแปรคุณไม่ต้องพึ่งพา ไฟล์ชั่วคราวใช้งานx=$(...)หรือแม้กระทั่งread a b c < <(...)) ดังนั้นนี่เป็นทางออกที่ถูกต้องเมื่อคุณรู้ว่าสิ่งที่คุณต้องการดึงในไฟล์ YAML และรู้วิธีเขียนเส้นทับทิมเพื่อเข้าถึงข้อมูลนี้ แม้ว่ามันจะหยาบ แต่ก็เป็นเครื่องพิสูจน์ความคิด IMHO อย่างเต็มรูปแบบ มันเป็นความจริงอย่างไรก็ตามมันไม่ได้ให้สิ่งที่เป็นนามธรรมแก่คุณ
vaab

ใช่แล้ว. คุณเข้มงวด ขอบคุณสำหรับเคล็ดลับนั้น การใช้ตัวแปรเดียวง่าย แต่ wariables จำนวนมากไม่ได้ หลอกลวงให้กับรายการตัวแปรอ่าน <<(การดำเนินการที่ stdout) เป็นประโยชน์มาก :)
Znik

23

เนื่องจาก Python3 และ PyYAML นั้นค่อนข้างง่ายที่จะพบเจอในปัจจุบันสิ่งต่อไปนี้อาจช่วยได้:

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

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

ฉันรักชามาล แต่ในระบบที่ไม่ได้เชื่อมต่อนี่เป็นตัวช่วยชีวิต ควรทำงานกับ python2 ส่วนใหญ่เช่นกัน RHEL
rsaw

2
อาจใช้yaml.safe_loadเพราะปลอดภัยกว่า pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

14

นี่เป็นรุ่นเพิ่มเติมของคำตอบของ 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);
      }
   }'
}

รุ่นนี้รองรับ-สัญกรณ์และสัญกรณ์สั้น ๆ สำหรับพจนานุกรมและรายการ อินพุตต่อไปนี้:

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

สร้างผลลัพธ์นี้:

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"

ในขณะที่คุณสามารถดู-รายการได้รับหมายเลขโดยอัตโนมัติเพื่อรับชื่อตัวแปรที่แตกต่างกันสำหรับแต่ละรายการ ในbashไม่มีอาร์เรย์หลายมิติดังนั้นนี่เป็นวิธีหนึ่งในการแก้ไข รองรับหลายระดับ หากต้องการแก้ไขปัญหาช่องว่างสีขาวต่อท้ายที่ระบุโดย @briceburg หนึ่งควรใส่ค่าในเครื่องหมายคำพูดเดี่ยวหรือคู่ อย่างไรก็ตามยังมีข้อ จำกัด บางประการ: การขยายพจนานุกรมและรายการสามารถให้ผลลัพธ์ที่ผิดเมื่อค่ามีเครื่องหมายจุลภาค นอกจากนี้โครงสร้างที่ซับซ้อนมากขึ้นเช่นค่าที่ครอบคลุมหลายบรรทัด (เช่น ssh-keys) ยังไม่รองรับ (ยัง)

คำสองสามคำเกี่ยวกับรหัส: sedคำสั่งแรกขยายรูปแบบย่อของพจนานุกรม{ key: value, ...}ให้เป็นแบบปกติและแปลงเป็นรูปแบบ yaml ที่ง่ายขึ้น การsedเรียกครั้งที่สองจะเหมือนกันสำหรับสัญกรณ์สั้น ๆ ของรายการและแปลง[ entry, ... ]เป็นรายการที่แยกรายการด้วย-สัญกรณ์ การsedเรียกครั้งที่สามเป็นฉบับดั้งเดิมที่จัดการพจนานุกรมปกติตอนนี้มีการเพิ่มการจัดการรายการด้วย-และการเยื้อง awkส่วนดัชนีแนะนำสำหรับแต่ละระดับเยื้องและการเพิ่มขึ้นก็เมื่อชื่อตัวแปรเป็นที่ว่างเปล่า (เช่นเมื่อการประมวลผลรายการ) ค่าปัจจุบันของตัวนับถูกใช้แทน vname ที่ว่างเปล่า เมื่อขึ้นไปหนึ่งระดับตัวนับจะเป็นศูนย์

แก้ไข: ฉันได้สร้างที่เก็บ githubสำหรับสิ่งนี้


11

ยากที่จะพูดเพราะมันขึ้นอยู่กับสิ่งที่คุณต้องการให้โปรแกรมแยกวิเคราะห์แยกจากเอกสาร YAML ของคุณ สำหรับกรณีที่เรียบง่ายคุณอาจจะสามารถที่จะใช้grep, cut, awkฯลฯ สำหรับการแยกวิเคราะห์ที่ซับซ้อนมากขึ้นคุณจะต้องใช้เต็มเป่าแยกห้องสมุดเช่น ธPyYAMLหรือYAML :: Perl


11

ฉันเพิ่งเขียนโปรแกรมแยกวิเคราะห์ที่ฉันเรียกว่าย่า! ( Yaml ไม่ใช่ Yamlesque! ) ซึ่งแยกYamlesqueซึ่งเป็นเซตย่อยของ YAML ดังนั้นหากคุณกำลังมองหาตัวแยกวิเคราะห์ YAML ที่สอดคล้องกับ Bash ได้ 100% นั่นไม่ใช่สิ่งนี้ อย่างไรก็ตามในการอ้างอิง OP หากคุณต้องการไฟล์การกำหนดค่าแบบโครงสร้างซึ่งเป็นเรื่องง่ายที่สุดสำหรับผู้ใช้ที่ไม่ใช่ด้านเทคนิคในการแก้ไขที่คล้ายกับ YAML นี่อาจเป็นที่สนใจ

มันถูกinspred โดยคำตอบก่อนหน้าแต่เขียนอาเรย์เชื่อมโยง ( ใช่มันต้อง Bash 4.x ) แทนตัวแปรพื้นฐาน มันทำในลักษณะที่ช่วยให้สามารถแยกวิเคราะห์ข้อมูลโดยไม่ทราบล่วงหน้าเกี่ยวกับคีย์เพื่อให้สามารถเขียนโค้ดที่ขับเคลื่อนด้วยข้อมูลได้

เช่นเดียวกับองค์ประกอบอาร์เรย์คีย์ / ค่าแต่ละอาร์เรย์มีkeysอาร์เรย์ที่มีรายการชื่อคีย์childrenอาร์เรย์ที่มีชื่ออาร์เรย์ย่อยและparentคีย์ที่อ้างอิงถึงแม่

นี่คือตัวอย่างของ 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

นี่คือตัวอย่างที่แสดงวิธีใช้:

#!/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

ผลลัพธ์ใด:

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

และนี่คือตัวแยกวิเคราะห์:

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 "$@"); }

มีเอกสารบางอย่างในไฟล์ต้นฉบับที่เชื่อมโยงและด้านล่างนี้เป็นคำอธิบายสั้น ๆ ว่าโค้ดทำอะไร

yay_parseฟังก์ชั่นแรกที่ตั้งอยู่inputไฟล์หรือทางออกที่มีสถานะออกจาก 1. ถัดไปก็จะเป็นตัวกำหนดชุดข้อมูลprefixทั้งระบุไว้อย่างชัดเจนหรือได้มาจากชื่อไฟล์

โดยจะเขียนbashคำสั่งที่ถูกต้องไปยังเอาต์พุตมาตรฐานซึ่งหากดำเนินการจะกำหนดอาร์เรย์ที่แสดงเนื้อหาของไฟล์ข้อมูลอินพุต ครั้งแรกของเหล่านี้กำหนดอาร์เรย์ระดับบนสุด:

echo "declare -g -A $prefix;"

โปรดทราบว่าการประกาศอาร์เรย์เป็นแบบเชื่อมโยง ( -A) ซึ่งเป็นคุณลักษณะของ Bash เวอร์ชัน 4 การประกาศเป็นแบบโกลบอล ( -g) ดังนั้นจึงสามารถเรียกใช้งานได้ในฟังก์ชัน แต่สามารถใช้งานได้ในขอบเขตส่วนกลางเช่นตัวyayช่วย:

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

sedป้อนข้อมูลจะถูกประมวลผลครั้งแรกกับ โดยจะลดบรรทัดที่ไม่ตรงกับข้อกำหนดคุณสมบัติการจัดรูปแบบ Yamlesque ก่อนที่จะลบเขตข้อมูล Yamlesque ที่ถูกต้องด้วยอักขระตัวคั่นไฟล์ ASCII และลบเครื่องหมายคำพูดคู่ใด ๆ ที่ล้อมรอบเขตข้อมูลค่า

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

นิพจน์ทั้งสองมีความคล้ายคลึงกัน พวกเขาแตกต่างกันเพียงเพราะคนแรกเลือกค่าที่ยกมาซึ่งเป็นคนที่สองเลือกค่าที่ไม่ยกมา

ตัวคั่นไฟล์ (28 / ฐานสิบหก 12 / ฐานแปด 034) ถูกใช้เนื่องจากเป็นอักขระที่ไม่สามารถพิมพ์ได้จึงไม่น่าจะอยู่ในข้อมูลอินพุต

ผลลัพธ์จะถูกไพพ์ไปawkที่กระบวนการอินพุตหนึ่งบรรทัดในแต่ละครั้ง มันใช้อักขระFSเพื่อกำหนดแต่ละฟิลด์ให้กับตัวแปร:

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

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

ถัดไปมันจะทำงานสิ่งที่prefixใช้สำหรับรายการปัจจุบัน นี่คือสิ่งที่ได้รับการเพิ่มชื่อคีย์เพื่อสร้างชื่ออาร์เรย์ มีroot_prefixสำหรับอาร์เรย์ระดับบนสุดซึ่งกำหนดเป็นชื่อชุดข้อมูลและขีดล่าง:

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

parent_keyเป็นกุญแจสำคัญที่ระดับเยื้องเหนือระดับเยื้องบรรทัดปัจจุบันและแสดงให้เห็นถึงคอลเลกชันที่บรรทัดปัจจุบันเป็นส่วนหนึ่งของ คอลเลกชันของคู่คีย์ / ค่าจะถูกเก็บไว้ในอาร์เรย์ที่มีชื่อของตนกำหนดเป็น concatenation ของที่และprefixparent_key

สำหรับระดับสูงสุด (ศูนย์เยื้องระดับ) คำนำหน้าชุดข้อมูลจะใช้เป็นคีย์หลักจึงไม่มีคำนำหน้า (ตั้งค่าเป็น"") อาร์เรย์อื่นทั้งหมดนำหน้าด้วยรูทนำหน้า

ถัดไปคีย์ปัจจุบันจะถูกแทรกลงในอาร์เรย์ (awk-internal) ที่มีคีย์ อาร์เรย์นี้ยังคงอยู่ตลอดเซสชัน awk ทั้งหมดและดังนั้นจึงมีปุ่มที่แทรกโดยบรรทัดก่อนหน้า คีย์ถูกแทรกเข้าไปในอาร์เรย์โดยใช้การเยื้องเป็นดัชนีดัชนี

keys[indent] = key;

เนื่องจากอาร์เรย์นี้มีคีย์จากบรรทัดก่อนหน้าคีย์ใด ๆ ที่มีตัวขูดระดับเยื้องกว่าระดับเยื้องของบรรทัดปัจจุบันจะถูกลบออก:

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

สิ่งนี้ทำให้อาร์เรย์ของคีย์ประกอบด้วยคีย์เชนจากรูทที่เยื้องระดับ 0 ถึงบรรทัดปัจจุบัน มันจะลบกุญแจค้างที่ยังคงอยู่เมื่อบรรทัดก่อนหน้าถูกเยื้องลึกกว่าบรรทัดปัจจุบัน

ส่วนสุดท้ายส่งออกbashคำสั่ง: บรรทัดอินพุตที่ไม่มีค่าจะเริ่มระดับเยื้องใหม่ ( คอลเลกชันในการพูดจาหยาบคาย) และบรรทัดอินพุตที่มีค่าจะเพิ่มคีย์ไปยังคอลเลกชันปัจจุบัน

ชื่อคอลเลกชันนี้เป็นกำหนดการบรรทัดปัจจุบันของและprefixparent_key

เมื่อคีย์มีค่าคีย์ที่มีค่านั้นจะถูกกำหนดให้กับคอลเลกชันปัจจุบันดังนี้:

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

คำสั่งแรกส่งคำสั่งเพื่อกำหนดค่าให้กับองค์ประกอบอาเรย์แบบเชื่อมโยงที่ตั้งชื่อตามคีย์และคำสั่งที่สองส่งคำสั่งเพื่อเพิ่มคีย์ลงในkeysรายการที่คั่นด้วยช่องว่างของคอลเลกชัน:

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

เมื่อคีย์ไม่มีค่าคอลเล็กชันใหม่จะเริ่มต้นดังนี้:

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

คำสั่งแรกส่งออกคำสั่งเพื่อเพิ่มคอลเลกชันใหม่ไปยังchildrenรายการที่คั่นด้วยช่องว่างของคอลเลกชันปัจจุบันและคำสั่งที่สองส่งคำสั่งเพื่อประกาศอาเรย์เชื่อมโยงใหม่สำหรับคอลเลกชันใหม่:

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

เอาต์พุตทั้งหมดจากyay_parseสามารถแยกวิเคราะห์เป็นคำสั่ง bash โดยคำสั่ง bash evalหรือsourcebuilt-in


คุณคิดว่าจะทำโครงการนี้กับ GitHub หรือไม่? หรือเป็นอยู่แล้ว?
daniel

@ Daniel มันอยู่ใน GitHub แต่ไม่ได้อยู่ใน repo ของตัวเอง - คุณสามารถหาได้ในที่นี่ ดูexamplesและusr/libไดเรกทอรีเหล่านี้เชื่อมโยงในคำตอบของคำถาม หากมีความสนใจที่ฉันสามารถแบ่งออกเป็น repo ของตัวเอง
starfry

4
รุ่งโรจน์ใน YAY ตอนแรกฉันเขียนมันใหม่เพื่อทุบตีอย่างบริสุทธิ์ แต่จากนั้นฉันก็ไม่สามารถหยุดตัวเองและนำมันกลับมาใช้ใหม่ในฐานะ parser พื้นฐานพร้อมการสนับสนุนอาร์เรย์และโครงสร้างที่ซ้อนกันซึ่งไม่สามารถก้าวข้ามชื่อของกันและกันได้ มันเป็นเรื่องที่github.com/binaryphile/y2s
Bile Phile

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

มีประโยชน์สำหรับการกำหนดค่าแบนเท่านั้น มันไม่สามารถใช้ได้กับ yaml ที่มีโครงสร้าง วิธีการป้องกันการใช้ไฟล์ชั่วคราว
Znik

5

อีกทางเลือกหนึ่งคือการแปลง YAML เป็น JSON จากนั้นใช้ jq เพื่อโต้ตอบกับการเป็นตัวแทน JSON เพื่อดึงข้อมูลจากมันหรือแก้ไข

ฉันเขียนสคริปต์ทุบตีง่าย ๆ ที่มีกาวนี้ - ดูโครงการ Y2J บน GitHub


2

หากคุณต้องการค่าเดียวที่คุณสามารถเครื่องมือที่แปลงเอกสาร YAML ของคุณเพื่อ JSON และอาหารไปยกตัวอย่างเช่นjqyq

เนื้อหาของตัวอย่าง yaml:

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

ตัวอย่าง:

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

1

ฉันรู้ว่านี่เฉพาะเจาะจงมาก แต่ฉันคิดว่าคำตอบของฉันอาจมีประโยชน์สำหรับผู้ใช้บางราย
ถ้าคุณมีnodeและติดตั้งในเครื่องของคุณคุณสามารถใช้npm ติดตั้งครั้งแรก:js-yaml

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

จากนั้นในสคริปต์ทุบตีของคุณ

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

นอกจากนี้หากคุณกำลังใช้งานjqคุณสามารถทำสิ่งนั้นได้

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

เพราะjs-yamlแปลงไฟล์ yaml เป็นสตริง json ตามตัวอักษร จากนั้นคุณสามารถใช้สตริงกับ json parser ใด ๆ ในระบบยูนิกซ์ของคุณ


1

ถ้าคุณมีงูหลามที่ 2 และ PyYAML คุณสามารถใช้ parser นี้ผมเขียนเรียกว่าparse_yaml.py สิ่งที่ neater บางตัวทำคือให้คุณเลือกคำนำหน้า (ในกรณีที่คุณมีมากกว่าหนึ่งไฟล์ที่มีตัวแปรที่คล้ายกัน) และเพื่อเลือกค่าเดียวจากไฟล์ yaml

ตัวอย่างเช่นหากคุณมีไฟล์ yaml เหล่านี้:

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

คุณสามารถโหลดได้ทั้งสองแบบโดยไม่มีข้อขัดแย้ง

$ 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

และแม้แต่เชอร์รี่ก็เลือกค่าที่คุณต้องการ

$ 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


0

คุณสามารถพิจารณาใช้Grunt (ตัวเรียกใช้งาน JavaScript) สามารถรวมเข้ากับเปลือกได้ง่าย สนับสนุนการอ่านไฟล์ YAML ( grunt.file.readYAML) และ JSON ( grunt.file.readJSON)

สิ่งนี้สามารถทำได้โดยการสร้างงานในGruntfile.js(หรือGruntfile.coffee) เช่น:

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':
        });
    });

};

จากนั้นเพียงแค่เรียกใช้จากเชลล์grunt foo(ตรวจสอบgrunt --helpงานที่มีอยู่)

ยิ่งไปกว่านั้นคุณสามารถใช้exec:fooงาน ( grunt-exec) โดยป้อนตัวแปรที่ส่งผ่านจากงานของคุณ ( foo: { cmd: 'echo bar <%= foo %>' }) เพื่อพิมพ์ผลลัพธ์ในรูปแบบที่คุณต้องการจากนั้นไปป์มันลงในคำสั่งอื่น


นอกจากนี้ยังมีเครื่องมือที่คล้ายกับฮึดฮัดก็เรียกว่าอึกด้วยปลั๊กอินเพิ่มเติมอึก-yaml

ติดตั้งผ่าน: npm install --save-dev gulp-yaml

ตัวอย่างการใช้งาน:

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/'))

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


เครื่องมืออื่น ๆ :

  • Jshon

    แยกวิเคราะห์อ่านและสร้าง JSON


0

ฉันรู้ว่าคำตอบของฉันนั้นเฉพาะเจาะจง แต่ถ้ามีการติดตั้งPHPและSymfonyไว้แล้วมันจะมีประโยชน์มากในการใช้ตัวแยกวิเคราะห์ YAML ของ Symfony

ตัวอย่างเช่น

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

ที่นี่ฉันใช้var_dumpเพื่อส่งออกอาร์เรย์ parsed แต่แน่นอนคุณสามารถทำอะไรได้อีกมากมาย ... :)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.