Bagaimana saya bisa mem-parsing file YAML dari skrip shell Linux?

192

Saya ingin menyediakan file konfigurasi terstruktur yang semudah mungkin untuk diedit oleh pengguna non-teknis (sayangnya itu harus berupa file) dan jadi saya ingin menggunakan YAML. Saya tidak dapat menemukan cara untuk menguraikan ini dari skrip shell Unix.

Yazz.com
sumber
tidak secara langsung pertanyaan Anda, tetapi Anda mungkin ingin melihat kemungkinan jika penulisan shell Anda terutama tentang berurusan dengan manajemen jarak jauh dari node yang berbeda (dan inventaris yaml)
eckes
9
Coba gunakan yquntuk membaca / menulis file yaml di shell. Halaman proyek ada di sini: mikefarah.github.io/yq Anda dapat menginstal alat dengan brew, aptatau mengunduh biner. Membaca nilai sesederhanayq r some.yaml key.value
vdimitrov
@kenorb JSON! = yml / YAML
swe
Saya menemukan fungsi terkait erat gitub pkuczynski yang terbaik (bagi saya) adalah bahwa dari jasperes, dipelihara di github sendiri
splaisan

Jawaban:

56

Kasing penggunaan saya mungkin atau mungkin tidak persis sama dengan apa yang ditanyakan oleh posting asli ini, tetapi pasti mirip.

Saya perlu menarik beberapa YAML sebagai variabel bash. YAML tidak akan pernah lebih dari satu level.

YAML terlihat seperti ini:

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

Output like-a dis:

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

Saya mencapai output dengan baris ini:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gmenemukan :dan menggantinya dengan =", sementara mengabaikan ://(untuk URL)
  • s/$/"/gditambahkan "ke akhir setiap baris
  • s/ *=/=/g menghapus semua spasi sebelumnya =
Curtis Blackwell
sumber
13
Tidak yakin apa yang Anda maksud, tetapi jika Anda maksud ini tidak bekerja untuk semua YAML, Anda benar. Itu sebabnya saya membuka dengan beberapa kualifikasi. Saya baru saja membagikan apa yang berfungsi untuk kasus penggunaan saya, karena itu menjawab pertanyaan lebih baik daripada yang lain pada saat itu. Ini pasti dapat diperluas.
Curtis Blackwell
3
sedikit terbuka untuk injeksi kode juga, tetapi seperti yang Anda katakan adalah langkah maju
Oriettaxx
1
Saya hanya pernah menulis skrip shell untuk digunakan secara lokal, jadi itu tidak menjadi masalah bagi saya. Namun, jika Anda tahu cara mengamankannya dan / atau ingin menguraikan, saya pasti akan berterima kasih.
Curtis Blackwell
2
Yaml satu tingkat memiliki banyak bentuk - nilai dapat dibagi menjadi mengikuti garis indentasi; nilai dapat dikutip dalam berbagai cara shell tidak akan diuraikan; semuanya dapat ditulis dalam satu baris dengan kawat gigi: {KEY: 'value', ...}; dan mungkin yang lainnya. Yang paling penting, jika Anda berniat mengevaluasi hasilnya sebagai kode shell, itu akan sangat tidak aman.
Beni Cherniavsky-Paskin
280

Berikut ini adalah pengurai bash-only yang memanfaatkan sed dan awk untuk mengurai file yaml sederhana:

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

Ini memahami file seperti:

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

## output
output:
   file: "yes"

Yang mana, ketika diuraikan menggunakan:

parse_yaml sample.yml

akan menampilkan:

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

ia juga memahami file yaml, dihasilkan oleh ruby ​​yang mungkin menyertakan simbol ruby, seperti:

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

dan akan menampilkan yang sama seperti pada contoh sebelumnya.

penggunaan khas dalam skrip adalah:

eval $(parse_yaml sample.yml)

parse_yaml menerima argumen awalan sehingga pengaturan yang diimpor semua memiliki awalan umum (yang akan mengurangi risiko tabrakan namespace).

parse_yaml sample.yml "CONF_"

hasil:

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

Perhatikan bahwa pengaturan sebelumnya dalam file dapat dirujuk dengan pengaturan selanjutnya:

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

## output
output:
   debug: $global_debug

Penggunaan bagus lainnya adalah mem-parsing file default terlebih dahulu dan kemudian pengaturan pengguna, yang berfungsi karena pengaturan yang terakhir menimpa yang pertama:

eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)
Stefan Farestam
sumber
3
Stefan keren! Akan luar biasa jika bisa mengubah -notasi yaml menjadi bash array asli juga!
quickshiftin
3
Itu seharusnya cukup mudah dilakukan jika Anda mengubah baris printf dalam skrip awk. Perhatikan bahwa bash tidak memiliki dukungan untuk array asosiatif multidimensi sehingga Anda berakhir dengan array + kunci tunggal per nilai. Hmm, mungkin sebaiknya pindahkan ini ke github ...
Stefan Farestam
5
Ini mengharapkan lekukan yml standar 2 spasi. Jika Anda menggunakan 4 spasi, maka variabel akan mendapatkan dua garis bawah sebagai pembatas, misalnya global__debugbukan global_debug.
k0pernikus
3
Hai vaab - Meskipun saya yakin Anda benar bahwa banyak pembaca ingin mem-parsing file YAML nyata dari shell, itu tidak cukup jelas (setidaknya bagi saya) apa hasilnya. Dengan skrip ini, saya telah menusuk masalah dan mendefinisikan subset yang memiliki pemetaan yang masuk akal ke dalam variabel standar. Jelas tidak ada alasan untuk mengatasi masalah yang lebih besar dari penguraian file YAML nyata.
Stefan Farestam
3
Ini hanya mencetak output di layar. Bagaimana Anda mengakses nilai nanti?
sattu
96

Saya telah menulis shyamldengan python untuk kebutuhan permintaan YAML dari baris perintah shell.

Gambaran:

$ pip install shyaml      ## installation

File YAML contoh (dengan fitur kompleks):

$ 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

Permintaan dasar:

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

Permintaan perulangan yang lebih kompleks pada nilai-nilai kompleks:

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

Beberapa poin kunci:

  • semua tipe YAML dan keanehan sintaks ditangani dengan benar, seperti multiline, string yang dikutip, urutan inline ...
  • \0 output empuk tersedia untuk manipulasi entri multiline padat.
  • notasi sederhana bertitik untuk memilih sub-nilai (yaitu: subvalue.maintaineradalah kunci yang valid).
  • akses menurut indeks disediakan untuk urutan (yaitu: subvalue.things.-1adalah elemen terakhir dari subvalue.thingsurutan.)
  • akses ke semua elemen urutan / struct dalam satu jalan untuk digunakan dalam loop bash.
  • Anda dapat menampilkan seluruh bagian dari file YAML sebagai ... YAML, yang berbaur dengan baik untuk manipulasi lebih lanjut dengan shyaml.

Lebih banyak sampel dan dokumentasi tersedia di halaman shyaml github atau halaman shyaml PyPI .

vaab
sumber
1
Ini luar biasa! Akan lebih bagus jika ada flag untuk mengabaikan nilai yaml yang kosong di output. Sekarang ini menghasilkan "null". Saya menggunakannya bersama dengan envdir untuk menampilkan file pembuatan docker ke envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket
@JiminyCricket Silakan gunakan halaman masalah github! Saya setidaknya akan senang untuk melacak ini. ;)
vaab
1
Sayangnya, shyamlsangat lambat
nowox
42

yq adalah prosesor YAML baris perintah yang ringan dan portabel

Tujuan dari proyek ini adalah untuk menjadi jq atau sed file yaml.

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

Sebagai contoh (dicuri langsung dari dokumentasi ), diberikan file sample.yaml dari:

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

kemudian

yq r sample.yaml bob.*.cats

akan menampilkan

- bananas
- apples
bupupin
sumber
itu hanya kurang kemampuan penyaringan
Antonin
formulae.brew.sh/formula/yq memiliki 26.679 instal selama setahun terakhir.
dustinevan
1
@Antonin Saya tidak yakin apakah ini yang Anda maksud tetapi sepertinya ia memiliki beberapa kemampuan penyaringan sekarang: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin
32

Dimungkinkan untuk mengirimkan skrip kecil ke beberapa penerjemah, seperti Python. Cara mudah untuk melakukannya menggunakan Ruby dan pustaka YAML-nya adalah sebagai berikut:

$ 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

, di mana datahash (atau array) dengan nilai-nilai dari yaml.

Sebagai bonus, itu akan mengurai masalah depan Jekyll baik-baik saja.

ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md
Rafael
sumber
1
apakah itu bisa digunakan? Anda telah menempatkan yaml oleh echo ke interpreter ruby. tetapi bagaimana seharusnya menggunakan variabel ini di bawah skrip bash?
Znik
Ya, itu bisa digunakan. The RUBY_SCRIPTvariabel adalah script ruby yang dapat ditulis ke file bukan (run dengan ruby -ryaml <rubyscript_filename>). Ini berisi logika untuk mengubah teks input menjadi beberapa teks output, secara internal menyimpan konten ke dalam datavariabel. Gema menghasilkan teks yaml, tetapi Anda dapat menggunakan cat <yaml_filename>untuk mem-pipe konten file sebagai gantinya.
Rafael
Maaf, saya tidak melihat ini dalam contoh di atas. Pada variabel pertama, RUBY_SCRIPT menyimpan kode untuk interpreter ruby. Selanjutnya echo -e mensimulasikan data yaml, ini dengan tumpukan dialihkan ke interpreter ruby. Ini menyebut kode ruby ​​sebagai skrip inline dan akhirnya mencetak ke variabel contoh 'a' dan 'b'. Lalu di mana variabel memuat ke bash untuk kode dieksekusi sisanya? Saya hanya melihat satu solusi. menempatkan ruby ​​outout ke temporary_file, yang seharusnya berisi baris: variabel = 'nilai', dan setelah itu memuatnya ke bash oleh '. temporary_file '. tapi ini solusinya, bukan resolusi.
Znik
1
@Znik setelah Anda mendapatkan sesuatu di stdout, diproduksi oleh sesuatu yang diberi makan dengan stdin, sisanya bergantung pada tangan bash coder (dan sebagai pengingat, jika Anda perlu stdoutdimasukkan ke dalam variabel, Anda tidak harus bergantung pada file sementara! gunakan x=$(...)atau bahkan read a b c < <(...)). Jadi, ini adalah solusi yang valid ketika Anda tahu persis apa yang ingin Anda ambil dalam file YAML dan tahu cara menulis garis ruby ​​untuk mengakses data ini. Bahkan jika itu kasar, itu adalah bukti konsep konsep IMHO. Memang benar bahwa itu tidak memberi Anda abstraksi bash penuh.
vaab
Ya itu. Kamu benar. Terima kasih untuk trik itu. Menggunakan satu variabel sederhana. tetapi banyak wariables tidak. trik dengan membaca daftar variabel <<(eksekusi ke stdout) sangat berguna :)
Znik
23

Mengingat bahwa Python3 dan PyYAML adalah dependensi yang cukup mudah untuk dipenuhi saat ini, berikut ini dapat membantu:

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

VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")
Torsten Bronger
sumber
Saya suka shyaml, tetapi pada sistem yang terputus ini adalah penyelamat. Harus bekerja dengan sebagian besar python2 juga, misalnya, RHEL.
rsaw
2
Mungkin digunakan yaml.safe_loadkarena lebih aman. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart
13

di sini versi lanjutan dari jawaban 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);
      }
   }'
}

Versi ini mendukung -notasi dan notasi singkat untuk kamus dan daftar. Input berikut:

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

menghasilkan output ini:

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"

karena Anda dapat melihat -item secara otomatis diberi nomor untuk mendapatkan nama variabel yang berbeda untuk setiap item. Di bashsana tidak ada array multidimensi, jadi ini adalah salah satu cara untuk mengatasinya. Beberapa level didukung. Untuk mengatasi masalah dengan membuntuti spasi putih yang disebutkan oleh @briceburg, seseorang harus menyertakan nilai dalam tanda kutip tunggal atau ganda. Namun, masih ada beberapa batasan: Perluasan kamus dan daftar dapat menghasilkan hasil yang salah ketika nilai mengandung koma. Juga, struktur yang lebih kompleks seperti nilai yang mencakup banyak garis (seperti kunci ssh) belum didukung.

Beberapa kata tentang kode: sedPerintah pertama memperluas bentuk kamus singkat { key: value, ...}menjadi reguler dan mengubahnya menjadi gaya yaml yang lebih sederhana. sedPanggilan kedua melakukan hal yang sama untuk notasi singkat daftar dan mengkonversi [ entry, ... ]ke daftar item dengan -notasi. sedPanggilan ketiga adalah yang asli yang menangani kamus normal, sekarang dengan tambahan untuk menangani daftar -dan lekukan. Bagian ini awkmemperkenalkan indeks untuk setiap tingkat lekukan dan meningkatkannya ketika nama variabel kosong (yaitu saat memproses daftar). Nilai saat ini dari penghitung digunakan sebagai ganti vname kosong. Saat naik satu tingkat, penghitungnya akan memusatkan perhatian.

Sunting: Saya telah membuat repositori github untuk ini.

Martin Hecht
sumber
11

Sulit dikatakan karena itu tergantung pada apa yang Anda ingin parser untuk mengekstrak dari dokumen YAML Anda. Untuk kasus sederhana, Anda mungkin dapat menggunakan grep, cut, awkdll Untuk lebih parsing kompleks Anda akan perlu menggunakan full-blown parsing perpustakaan seperti Python PyYAML atau YAML :: Perl .

dogbane
sumber
11

Saya baru saja menulis parser yang saya sebut Yay! ( Yaml bukan Yamlesque! ) Yang mem-parsing Yamlesque , sebagian kecil dari YAML. Jadi, jika Anda mencari parser YAML yang 100% sesuai untuk Bash maka ini bukan. Namun, untuk mengutip OP, jika Anda ingin file konfigurasi terstruktur yang semudah mungkin untuk pengguna non-teknis untuk mengedit yang seperti YAML, ini mungkin menarik.

Itu diinspirasi oleh jawaban sebelumnya tetapi menulis array asosiatif ( ya, ini membutuhkan Bash 4.x ) bukan variabel dasar. Ia melakukannya dengan cara yang memungkinkan data diurai tanpa pengetahuan sebelumnya tentang kunci sehingga kode yang digerakkan oleh data dapat ditulis.

Selain elemen array kunci / nilai, setiap array memiliki keysarray yang berisi daftar nama kunci, childrenarray yang berisi nama array anak dan parentkunci yang merujuk ke induknya.

Ini adalah contoh dari 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

Berikut adalah contoh yang menunjukkan cara menggunakannya:

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

yang keluaran:

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

Dan di sini adalah pengurai:

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

Ada beberapa dokumentasi dalam file sumber tertaut dan di bawah ini adalah penjelasan singkat tentang apa yang kode lakukan.

The yay_parseFungsi pertama menempatkan inputfile atau keluar dengan status keluar dari 1. Berikutnya, menentukan dataset prefix, baik secara eksplisit ditentukan atau berasal dari nama file.

Ini menulis bashperintah yang valid untuk output standar yang, jika dijalankan, menentukan array yang mewakili konten file data input. Yang pertama dari ini mendefinisikan array tingkat atas:

echo "declare -g -A $prefix;"

Perhatikan bahwa deklarasi array adalah asosiatif ( -A) yang merupakan fitur dari Bash versi 4. Deklarasi juga global ( -g) sehingga dapat dieksekusi dalam suatu fungsi tetapi tersedia untuk lingkup global seperti yayhelper:

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

Data input awalnya diproses dengan sed. Ini menghapus garis yang tidak cocok dengan spesifikasi format Yamlesque sebelum membatasi bidang Yamlesque yang valid dengan karakter Pemisah File ASCII dan menghapus tanda kutip ganda di sekitar bidang nilai.

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

Kedua ungkapan itu serupa; mereka berbeda hanya karena yang pertama mengambil nilai yang dikutip sedangkan yang kedua memilih yang tidak dikutip.

The Berkas Separator (28 / hex 12 / oktal 034) digunakan karena, sebagai karakter non-printable, itu tidak mungkin dalam data masukan.

Hasilnya disalurkan ke awkmana memproses inputnya satu baris pada satu waktu. Ini menggunakan karakter FS untuk menetapkan setiap bidang ke variabel:

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

Semua baris memiliki indent (mungkin nol) dan kunci tetapi mereka tidak semua memiliki nilai. Ini menghitung tingkat indentasi untuk garis yang membagi panjang bidang pertama, yang berisi spasi putih terkemuka, sebanyak dua. Item tingkat atas tanpa indentasi berada pada level indent nol.

Selanjutnya, ia mengetahui apa yang prefixharus digunakan untuk item saat ini. Inilah yang akan ditambahkan ke nama kunci untuk membuat nama array. Ada untuk root_prefixlarik level atas yang didefinisikan sebagai nama kumpulan data dan garis bawah:

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

Ini parent_keyadalah kunci pada level indent di atas level indentasi baris saat ini dan mewakili koleksi yang menjadi bagian dari baris saat ini. Pasangan kunci / nilai koleksi akan disimpan dalam array dengan namanya didefinisikan sebagai gabungan dari prefixdan parent_key.

Untuk tingkat atas (tingkat indentasi nol) awalan kumpulan data digunakan sebagai kunci induk sehingga tidak memiliki awalan (diatur ke ""). Semua array lainnya diawali dengan awalan root.

Selanjutnya, kunci saat ini dimasukkan ke dalam array (awk-internal) yang berisi kunci. Array ini tetap ada sepanjang seluruh sesi awk dan karenanya berisi kunci yang dimasukkan oleh baris sebelumnya. Kunci tersebut dimasukkan ke dalam array menggunakan indentasinya sebagai indeks array.

keys[indent] = key;

Karena array ini berisi kunci dari baris sebelumnya, kunci apa pun dengan parutan level indentasi dari level indentasi baris saat ini dihapus:

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

Ini meninggalkan array kunci yang berisi gantungan kunci dari root pada level indent 0 ke baris saat ini. Ini menghapus kunci basi yang tersisa ketika baris sebelumnya indentasi lebih dalam dari garis saat ini.

Bagian terakhir menampilkan bashperintah: baris input tanpa nilai memulai level indent baru ( koleksi dalam bahasa YAML) dan baris input dengan nilai menambahkan kunci ke koleksi saat ini.

Nama koleksi adalah gabungan dari baris saat ini prefixdan parent_key.

Ketika kunci memiliki nilai, kunci dengan nilai itu ditetapkan untuk koleksi saat ini seperti ini:

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

Pernyataan pertama menghasilkan perintah untuk menetapkan nilai ke elemen array asosiatif yang dinamai setelah kunci dan yang kedua mengeluarkan perintah untuk menambahkan kunci ke keysdaftar dibatasi-ruang koleksi :

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

Ketika kunci tidak memiliki nilai, koleksi baru dimulai seperti ini:

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

Pernyataan pertama menghasilkan perintah untuk menambahkan koleksi baru ke childrendaftar terbatas-ruang koleksi saat ini dan yang kedua menampilkan perintah untuk mendeklarasikan array asosiatif baru untuk koleksi baru:

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

Semua output dari yay_parsedapat diurai sebagai perintah bash oleh perintah bash evalatau sourcebuilt-in.

starfry
sumber
Sudahkah Anda mempertimbangkan menjadikan ini proyek di GitHub? Atau sudah?
daniel
@aniel, ada di GitHub tetapi tidak dalam repo sendiri - Anda dapat menemukannya di sini . Lihat examplesdan usr/libdirektori, Ini terkait dalam jawaban saya untuk pertanyaan itu. Jika ada minat saya bisa memecahnya menjadi repo sendiri.
Starfry
4
Kudos pada YAY. Pada awalnya, saya menulis ulang itu menjadi bash murni, tetapi kemudian saya tidak bisa menghentikan diri saya dan mengimplementasikannya kembali sebagai parser dasar dengan dukungan untuk array dan struktur bersarang yang tidak dapat menginjak nama masing-masing. Ada di github.com/binaryphile/y2s .
Binary Phile
5
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh
mushuweasel
sumber
hanya berguna untuk konfigurasi datar. itu tidak berlaku untuk yaml terstruktur. lain, bagaimana mencegah menggunakan file.sh sementara?
Znik
5

Pilihan lain adalah mengonversi YAML ke JSON, kemudian menggunakan jq untuk berinteraksi dengan representasi JSON baik untuk mengekstrak informasi darinya atau mengeditnya.

Saya menulis skrip bash sederhana yang berisi lem ini - lihat proyek Y2J di GitHub

hanya untukmu
sumber
2

Jika Anda membutuhkan nilai tunggal, Anda bisa menggunakan alat yang mengubah dokumen YAML Anda menjadi JSON dan memberi makan jq, misalnya yq.

Isi dari sampel. Yaml:

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

Contoh:

$ yq -r '.bob["thing"]["cats"]' sample.yaml 
oranges
Rick van der Zwet
sumber
1

Saya tahu ini sangat spesifik, tetapi saya pikir jawaban saya dapat bermanfaat bagi pengguna tertentu.
Jika Anda memiliki nodedan npmmenginstal pada mesin Anda, Anda dapat menggunakan js-yaml.
Instal pertama:

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

lalu di skrip bash Anda

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

Juga jika Anda menggunakan jqAnda dapat melakukan sesuatu seperti itu

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

Karena js-yaml mengkonversi file yaml ke string json literal. Anda kemudian dapat menggunakan string dengan parser json di sistem unix Anda.

vdegenne
sumber
1

Jika Anda memiliki python 2 dan PyYAML, Anda dapat menggunakan parser ini yang saya tulis disebut parse_yaml.py . Beberapa hal yang lebih rapi yang dilakukannya adalah membiarkan Anda memilih awalan (jika Anda memiliki lebih dari satu file dengan variabel yang sama) dan untuk mengambil nilai tunggal dari file yaml.

Sebagai contoh jika Anda memiliki file yaml ini:

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

Anda dapat memuat keduanya tanpa konflik.

$ 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

Dan bahkan cherry memilih nilai yang Anda inginkan.

$ 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
bahaya
sumber
1

Anda bisa menggunakan setara dengan yq yang ditulis dalam golang:

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

pengembalian:

62.0.3
030
sumber
0

Anda juga dapat mempertimbangkan menggunakan Grunt (Pelari Tugas JavaScript). Dapat dengan mudah diintegrasikan dengan shell. Ini mendukung pembacaan YAML ( grunt.file.readYAML) dan JSON (grunt.file.readJSON file ).

Ini dapat dicapai dengan membuat tugas di Gruntfile.js(atau Gruntfile.coffee), misalnya:

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

};

kemudian dari shell cukup jalankan grunt foo(periksa grunt --helptugas yang tersedia).

Lebih jauh lagi, Anda dapat mengimplementasikan exec:footugas ( grunt-exec) dengan variabel input yang lulus dari tugas Anda ( foo: { cmd: 'echo bar <%= foo %>' }) untuk mencetak output dalam format apa pun yang Anda inginkan, kemudian menyalurkannya ke perintah lain.


Ada juga alat yang mirip dengan Grunt, itu disebut gulp dengan tambahan plugin gulp-yaml .

Instal melalui: npm install --save-dev gulp-yaml

Penggunaan sampel:

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

Untuk lebih banyak opsi untuk berurusan dengan format YAML , periksa situs YAML untuk proyek, perpustakaan, dan sumber daya lain yang tersedia yang dapat membantu Anda untuk mem-parsing format itu.


Alat lainnya:

  • Jshon

    mem-parsing, membaca dan membuat JSON

kenorb
sumber
0

Saya tahu jawaban saya spesifik, tetapi jika seseorang sudah menginstal PHP dan Symfony , akan sangat berguna untuk menggunakan parser YAML milik Symfony.

Misalnya:

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

Di sini saya hanya digunakan var_dumpuntuk menampilkan parsing array tetapi tentu saja Anda dapat melakukan lebih banyak lagi ... :)

fbastien
sumber